diff options
Diffstat (limited to 'arch/tile/mm/highmem.c')
| -rw-r--r-- | arch/tile/mm/highmem.c | 328 | 
1 files changed, 328 insertions, 0 deletions
diff --git a/arch/tile/mm/highmem.c b/arch/tile/mm/highmem.c new file mode 100644 index 00000000000..1fcecc5b9e0 --- /dev/null +++ b/arch/tile/mm/highmem.c @@ -0,0 +1,328 @@ +/* + * Copyright 2010 Tilera Corporation. All Rights Reserved. + * + *   This program is free software; you can redistribute it and/or + *   modify it under the terms of the GNU General Public License + *   as published by the Free Software Foundation, version 2. + * + *   This program is distributed in the hope that it will be useful, but + *   WITHOUT ANY WARRANTY; without even the implied warranty of + *   MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + *   NON INFRINGEMENT.  See the GNU General Public License for + *   more details. + */ + +#include <linux/highmem.h> +#include <linux/module.h> +#include <linux/pagemap.h> +#include <asm/homecache.h> + +#define kmap_get_pte(vaddr) \ +	pte_offset_kernel(pmd_offset(pud_offset(pgd_offset_k(vaddr), (vaddr)),\ +		(vaddr)), (vaddr)) + + +void *kmap(struct page *page) +{ +	void *kva; +	unsigned long flags; +	pte_t *ptep; + +	might_sleep(); +	if (!PageHighMem(page)) +		return page_address(page); +	kva = kmap_high(page); + +	/* +	 * Rewrite the PTE under the lock.  This ensures that the page +	 * is not currently migrating. +	 */ +	ptep = kmap_get_pte((unsigned long)kva); +	flags = homecache_kpte_lock(); +	set_pte_at(&init_mm, kva, ptep, mk_pte(page, page_to_kpgprot(page))); +	homecache_kpte_unlock(flags); + +	return kva; +} +EXPORT_SYMBOL(kmap); + +void kunmap(struct page *page) +{ +	if (in_interrupt()) +		BUG(); +	if (!PageHighMem(page)) +		return; +	kunmap_high(page); +} +EXPORT_SYMBOL(kunmap); + +static void debug_kmap_atomic_prot(enum km_type type) +{ +#ifdef CONFIG_DEBUG_HIGHMEM +	static unsigned warn_count = 10; + +	if (unlikely(warn_count == 0)) +		return; + +	if (unlikely(in_interrupt())) { +		if (in_irq()) { +			if (type != KM_IRQ0 && type != KM_IRQ1 && +			    type != KM_BIO_SRC_IRQ && +			    /* type != KM_BIO_DST_IRQ && */ +			    type != KM_BOUNCE_READ) { +				WARN_ON(1); +				warn_count--; +			} +		} else if (!irqs_disabled()) {	/* softirq */ +			if (type != KM_IRQ0 && type != KM_IRQ1 && +			    type != KM_SOFTIRQ0 && type != KM_SOFTIRQ1 && +			    type != KM_SKB_SUNRPC_DATA && +			    type != KM_SKB_DATA_SOFTIRQ && +			    type != KM_BOUNCE_READ) { +				WARN_ON(1); +				warn_count--; +			} +		} +	} + +	if (type == KM_IRQ0 || type == KM_IRQ1 || type == KM_BOUNCE_READ || +	    type == KM_BIO_SRC_IRQ /* || type == KM_BIO_DST_IRQ */) { +		if (!irqs_disabled()) { +			WARN_ON(1); +			warn_count--; +		} +	} else if (type == KM_SOFTIRQ0 || type == KM_SOFTIRQ1) { +		if (irq_count() == 0 && !irqs_disabled()) { +			WARN_ON(1); +			warn_count--; +		} +	} +#endif +} + +/* + * Describe a single atomic mapping of a page on a given cpu at a + * given address, and allow it to be linked into a list. + */ +struct atomic_mapped_page { +	struct list_head list; +	struct page *page; +	int cpu; +	unsigned long va; +}; + +static spinlock_t amp_lock = __SPIN_LOCK_UNLOCKED(&_lock); +static struct list_head amp_list = LIST_HEAD_INIT(amp_list); + +/* + * Combining this structure with a per-cpu declaration lets us give + * each cpu an atomic_mapped_page structure per type. + */ +struct kmap_amps { +	struct atomic_mapped_page per_type[KM_TYPE_NR]; +}; +DEFINE_PER_CPU(struct kmap_amps, amps); + +/* + * Add a page and va, on this cpu, to the list of kmap_atomic pages, + * and write the new pte to memory.  Writing the new PTE under the + * lock guarantees that it is either on the list before migration starts + * (if we won the race), or set_pte() sets the migrating bit in the PTE + * (if we lost the race).  And doing it under the lock guarantees + * that when kmap_atomic_fix_one_pte() comes along, it finds a valid + * PTE in memory, iff the mapping is still on the amp_list. + * + * Finally, doing it under the lock lets us safely examine the page + * to see if it is immutable or not, for the generic kmap_atomic() case. + * If we examine it earlier we are exposed to a race where it looks + * writable earlier, but becomes immutable before we write the PTE. + */ +static void kmap_atomic_register(struct page *page, enum km_type type, +				 unsigned long va, pte_t *ptep, pte_t pteval) +{ +	unsigned long flags; +	struct atomic_mapped_page *amp; + +	flags = homecache_kpte_lock(); +	spin_lock(&_lock); + +	/* With interrupts disabled, now fill in the per-cpu info. */ +	amp = &__get_cpu_var(amps).per_type[type]; +	amp->page = page; +	amp->cpu = smp_processor_id(); +	amp->va = va; + +	/* For generic kmap_atomic(), choose the PTE writability now. */ +	if (!pte_read(pteval)) +		pteval = mk_pte(page, page_to_kpgprot(page)); + +	list_add(&->list, &_list); +	set_pte(ptep, pteval); +	arch_flush_lazy_mmu_mode(); + +	spin_unlock(&_lock); +	homecache_kpte_unlock(flags); +} + +/* + * Remove a page and va, on this cpu, from the list of kmap_atomic pages. + * Linear-time search, but we count on the lists being short. + * We don't need to adjust the PTE under the lock (as opposed to the + * kmap_atomic_register() case), since we're just unconditionally + * zeroing the PTE after it's off the list. + */ +static void kmap_atomic_unregister(struct page *page, unsigned long va) +{ +	unsigned long flags; +	struct atomic_mapped_page *amp; +	int cpu = smp_processor_id(); +	spin_lock_irqsave(&_lock, flags); +	list_for_each_entry(amp, &_list, list) { +		if (amp->page == page && amp->cpu == cpu && amp->va == va) +			break; +	} +	BUG_ON(&->list == &_list); +	list_del(&->list); +	spin_unlock_irqrestore(&_lock, flags); +} + +/* Helper routine for kmap_atomic_fix_kpte(), below. */ +static void kmap_atomic_fix_one_kpte(struct atomic_mapped_page *amp, +				     int finished) +{ +	pte_t *ptep = kmap_get_pte(amp->va); +	if (!finished) { +		set_pte(ptep, pte_mkmigrate(*ptep)); +		flush_remote(0, 0, NULL, amp->va, PAGE_SIZE, PAGE_SIZE, +			     cpumask_of(amp->cpu), NULL, 0); +	} else { +		/* +		 * Rewrite a default kernel PTE for this page. +		 * We rely on the fact that set_pte() writes the +		 * present+migrating bits last. +		 */ +		pte_t pte = mk_pte(amp->page, page_to_kpgprot(amp->page)); +		set_pte(ptep, pte); +	} +} + +/* + * This routine is a helper function for homecache_fix_kpte(); see + * its comments for more information on the "finished" argument here. + * + * Note that we hold the lock while doing the remote flushes, which + * will stall any unrelated cpus trying to do kmap_atomic operations. + * We could just update the PTEs under the lock, and save away copies + * of the structs (or just the va+cpu), then flush them after we + * release the lock, but it seems easier just to do it all under the lock. + */ +void kmap_atomic_fix_kpte(struct page *page, int finished) +{ +	struct atomic_mapped_page *amp; +	unsigned long flags; +	spin_lock_irqsave(&_lock, flags); +	list_for_each_entry(amp, &_list, list) { +		if (amp->page == page) +			kmap_atomic_fix_one_kpte(amp, finished); +	} +	spin_unlock_irqrestore(&_lock, flags); +} + +/* + * kmap_atomic/kunmap_atomic is significantly faster than kmap/kunmap + * because the kmap code must perform a global TLB invalidation when + * the kmap pool wraps. + * + * Note that they may be slower than on x86 (etc.) because unlike on + * those platforms, we do have to take a global lock to map and unmap + * pages on Tile (see above). + * + * When holding an atomic kmap is is not legal to sleep, so atomic + * kmaps are appropriate for short, tight code paths only. + */ +void *kmap_atomic_prot(struct page *page, enum km_type type, pgprot_t prot) +{ +	enum fixed_addresses idx; +	unsigned long vaddr; +	pte_t *pte; + +	/* even !CONFIG_PREEMPT needs this, for in_atomic in do_page_fault */ +	pagefault_disable(); + +	/* Avoid icache flushes by disallowing atomic executable mappings. */ +	BUG_ON(pte_exec(prot)); + +	if (!PageHighMem(page)) +		return page_address(page); + +	debug_kmap_atomic_prot(type); + +	idx = type + KM_TYPE_NR*smp_processor_id(); +	vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx); +	pte = kmap_get_pte(vaddr); +	BUG_ON(!pte_none(*pte)); + +	/* Register that this page is mapped atomically on this cpu. */ +	kmap_atomic_register(page, type, vaddr, pte, mk_pte(page, prot)); + +	return (void *)vaddr; +} +EXPORT_SYMBOL(kmap_atomic_prot); + +void *kmap_atomic(struct page *page, enum km_type type) +{ +	/* PAGE_NONE is a magic value that tells us to check immutability. */ +	return kmap_atomic_prot(page, type, PAGE_NONE); +} +EXPORT_SYMBOL(kmap_atomic); + +void kunmap_atomic(void *kvaddr, enum km_type type) +{ +	unsigned long vaddr = (unsigned long) kvaddr & PAGE_MASK; +	enum fixed_addresses idx = type + KM_TYPE_NR*smp_processor_id(); + +	/* +	 * Force other mappings to Oops if they try to access this pte without +	 * first remapping it.  Keeping stale mappings around is a bad idea. +	 */ +	if (vaddr == __fix_to_virt(FIX_KMAP_BEGIN+idx)) { +		pte_t *pte = kmap_get_pte(vaddr); +		pte_t pteval = *pte; +		BUG_ON(!pte_present(pteval) && !pte_migrating(pteval)); +		kmap_atomic_unregister(pte_page(pteval), vaddr); +		kpte_clear_flush(pte, vaddr); +	} else { +		/* Must be a lowmem page */ +		BUG_ON(vaddr < PAGE_OFFSET); +		BUG_ON(vaddr >= (unsigned long)high_memory); +	} + +	arch_flush_lazy_mmu_mode(); +	pagefault_enable(); +} +EXPORT_SYMBOL(kunmap_atomic); + +/* + * This API is supposed to allow us to map memory without a "struct page". + * Currently we don't support this, though this may change in the future. + */ +void *kmap_atomic_pfn(unsigned long pfn, enum km_type type) +{ +	return kmap_atomic(pfn_to_page(pfn), type); +} +void *kmap_atomic_prot_pfn(unsigned long pfn, enum km_type type, pgprot_t prot) +{ +	return kmap_atomic_prot(pfn_to_page(pfn), type, prot); +} + +struct page *kmap_atomic_to_page(void *ptr) +{ +	pte_t *pte; +	unsigned long vaddr = (unsigned long)ptr; + +	if (vaddr < FIXADDR_START) +		return virt_to_page(ptr); + +	pte = kmap_get_pte(vaddr); +	return pte_page(*pte); +}  |