|  | /* | 
|  | * Copyright (C) 2001-2005 Silicon Graphics, Inc.  All rights reserved. | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or modify it | 
|  | * under the terms of version 2 of the GNU General Public License | 
|  | * as published by the Free Software Foundation. | 
|  | * | 
|  | * A simple uncached page allocator using the generic allocator. This | 
|  | * allocator first utilizes the spare (spill) pages found in the EFI | 
|  | * memmap and will then start converting cached pages to uncached ones | 
|  | * at a granule at a time. Node awareness is implemented by having a | 
|  | * pool of pages per node. | 
|  | */ | 
|  |  | 
|  | #include <linux/types.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/errno.h> | 
|  | #include <linux/string.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/efi.h> | 
|  | #include <linux/genalloc.h> | 
|  | #include <asm/page.h> | 
|  | #include <asm/pal.h> | 
|  | #include <asm/system.h> | 
|  | #include <asm/pgtable.h> | 
|  | #include <asm/atomic.h> | 
|  | #include <asm/tlbflush.h> | 
|  | #include <asm/sn/arch.h> | 
|  |  | 
|  | #define DEBUG	0 | 
|  |  | 
|  | #if DEBUG | 
|  | #define dprintk			printk | 
|  | #else | 
|  | #define dprintk(x...)		do { } while (0) | 
|  | #endif | 
|  |  | 
|  | void __init efi_memmap_walk_uc (efi_freemem_callback_t callback); | 
|  |  | 
|  | #define MAX_UNCACHED_GRANULES	5 | 
|  | static int allocated_granules; | 
|  |  | 
|  | struct gen_pool *uncached_pool[MAX_NUMNODES]; | 
|  |  | 
|  |  | 
|  | static void uncached_ipi_visibility(void *data) | 
|  | { | 
|  | int status; | 
|  |  | 
|  | status = ia64_pal_prefetch_visibility(PAL_VISIBILITY_PHYSICAL); | 
|  | if ((status != PAL_VISIBILITY_OK) && | 
|  | (status != PAL_VISIBILITY_OK_REMOTE_NEEDED)) | 
|  | printk(KERN_DEBUG "pal_prefetch_visibility() returns %i on " | 
|  | "CPU %i\n", status, get_cpu()); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void uncached_ipi_mc_drain(void *data) | 
|  | { | 
|  | int status; | 
|  | status = ia64_pal_mc_drain(); | 
|  | if (status) | 
|  | printk(KERN_WARNING "ia64_pal_mc_drain() failed with %i on " | 
|  | "CPU %i\n", status, get_cpu()); | 
|  | } | 
|  |  | 
|  |  | 
|  | static unsigned long | 
|  | uncached_get_new_chunk(struct gen_pool *poolp) | 
|  | { | 
|  | struct page *page; | 
|  | void *tmp; | 
|  | int status, i; | 
|  | unsigned long addr, node; | 
|  |  | 
|  | if (allocated_granules >= MAX_UNCACHED_GRANULES) | 
|  | return 0; | 
|  |  | 
|  | node = poolp->private; | 
|  | page = alloc_pages_node(node, GFP_KERNEL | __GFP_ZERO, | 
|  | IA64_GRANULE_SHIFT-PAGE_SHIFT); | 
|  |  | 
|  | dprintk(KERN_INFO "get_new_chunk page %p, addr %lx\n", | 
|  | page, (unsigned long)(page-vmem_map) << PAGE_SHIFT); | 
|  |  | 
|  | /* | 
|  | * Do magic if no mem on local node! XXX | 
|  | */ | 
|  | if (!page) | 
|  | return 0; | 
|  | tmp = page_address(page); | 
|  |  | 
|  | /* | 
|  | * There's a small race here where it's possible for someone to | 
|  | * access the page through /dev/mem halfway through the conversion | 
|  | * to uncached - not sure it's really worth bothering about | 
|  | */ | 
|  | for (i = 0; i < (IA64_GRANULE_SIZE / PAGE_SIZE); i++) | 
|  | SetPageUncached(&page[i]); | 
|  |  | 
|  | flush_tlb_kernel_range(tmp, tmp + IA64_GRANULE_SIZE); | 
|  |  | 
|  | status = ia64_pal_prefetch_visibility(PAL_VISIBILITY_PHYSICAL); | 
|  |  | 
|  | dprintk(KERN_INFO "pal_prefetch_visibility() returns %i on cpu %i\n", | 
|  | status, get_cpu()); | 
|  |  | 
|  | if (!status) { | 
|  | status = smp_call_function(uncached_ipi_visibility, NULL, 0, 1); | 
|  | if (status) | 
|  | printk(KERN_WARNING "smp_call_function failed for " | 
|  | "uncached_ipi_visibility! (%i)\n", status); | 
|  | } | 
|  |  | 
|  | if (ia64_platform_is("sn2")) | 
|  | sn_flush_all_caches((unsigned long)tmp, IA64_GRANULE_SIZE); | 
|  | else | 
|  | flush_icache_range((unsigned long)tmp, | 
|  | (unsigned long)tmp+IA64_GRANULE_SIZE); | 
|  |  | 
|  | ia64_pal_mc_drain(); | 
|  | status = smp_call_function(uncached_ipi_mc_drain, NULL, 0, 1); | 
|  | if (status) | 
|  | printk(KERN_WARNING "smp_call_function failed for " | 
|  | "uncached_ipi_mc_drain! (%i)\n", status); | 
|  |  | 
|  | addr = (unsigned long)tmp - PAGE_OFFSET + __IA64_UNCACHED_OFFSET; | 
|  |  | 
|  | allocated_granules++; | 
|  | return addr; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | * uncached_alloc_page | 
|  | * | 
|  | * Allocate 1 uncached page. Allocates on the requested node. If no | 
|  | * uncached pages are available on the requested node, roundrobin starting | 
|  | * with higher nodes. | 
|  | */ | 
|  | unsigned long | 
|  | uncached_alloc_page(int nid) | 
|  | { | 
|  | unsigned long maddr; | 
|  |  | 
|  | maddr = gen_pool_alloc(uncached_pool[nid], PAGE_SIZE); | 
|  |  | 
|  | dprintk(KERN_DEBUG "uncached_alloc_page returns %lx on node %i\n", | 
|  | maddr, nid); | 
|  |  | 
|  | /* | 
|  | * If no memory is availble on our local node, try the | 
|  | * remaining nodes in the system. | 
|  | */ | 
|  | if (!maddr) { | 
|  | int i; | 
|  |  | 
|  | for (i = MAX_NUMNODES - 1; i >= 0; i--) { | 
|  | if (i == nid || !node_online(i)) | 
|  | continue; | 
|  | maddr = gen_pool_alloc(uncached_pool[i], PAGE_SIZE); | 
|  | dprintk(KERN_DEBUG "uncached_alloc_page alternate search " | 
|  | "returns %lx on node %i\n", maddr, i); | 
|  | if (maddr) { | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return maddr; | 
|  | } | 
|  | EXPORT_SYMBOL(uncached_alloc_page); | 
|  |  | 
|  |  | 
|  | /* | 
|  | * uncached_free_page | 
|  | * | 
|  | * Free a single uncached page. | 
|  | */ | 
|  | void | 
|  | uncached_free_page(unsigned long maddr) | 
|  | { | 
|  | int node; | 
|  |  | 
|  | node = paddr_to_nid(maddr - __IA64_UNCACHED_OFFSET); | 
|  |  | 
|  | dprintk(KERN_DEBUG "uncached_free_page(%lx) on node %i\n", maddr, node); | 
|  |  | 
|  | if ((maddr & (0XFUL << 60)) != __IA64_UNCACHED_OFFSET) | 
|  | panic("uncached_free_page invalid address %lx\n", maddr); | 
|  |  | 
|  | gen_pool_free(uncached_pool[node], maddr, PAGE_SIZE); | 
|  | } | 
|  | EXPORT_SYMBOL(uncached_free_page); | 
|  |  | 
|  |  | 
|  | /* | 
|  | * uncached_build_memmap, | 
|  | * | 
|  | * Called at boot time to build a map of pages that can be used for | 
|  | * memory special operations. | 
|  | */ | 
|  | static int __init | 
|  | uncached_build_memmap(unsigned long start, unsigned long end, void *arg) | 
|  | { | 
|  | long length; | 
|  | unsigned long vstart, vend; | 
|  | int node; | 
|  |  | 
|  | length = end - start; | 
|  | vstart = start + __IA64_UNCACHED_OFFSET; | 
|  | vend = end + __IA64_UNCACHED_OFFSET; | 
|  |  | 
|  | dprintk(KERN_ERR "uncached_build_memmap(%lx %lx)\n", start, end); | 
|  |  | 
|  | memset((char *)vstart, 0, length); | 
|  |  | 
|  | node = paddr_to_nid(start); | 
|  |  | 
|  | for (; vstart < vend ; vstart += PAGE_SIZE) { | 
|  | dprintk(KERN_INFO "sticking %lx into the pool!\n", vstart); | 
|  | gen_pool_free(uncached_pool[node], vstart, PAGE_SIZE); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | static int __init uncached_init(void) { | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < MAX_NUMNODES; i++) { | 
|  | if (!node_online(i)) | 
|  | continue; | 
|  | uncached_pool[i] = gen_pool_create(0, IA64_GRANULE_SHIFT, | 
|  | &uncached_get_new_chunk, i); | 
|  | } | 
|  |  | 
|  | efi_memmap_walk_uc(uncached_build_memmap); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | __initcall(uncached_init); |