| /* | |
| * 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, raw_smp_processor_id()); | |
| } | |
| 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, raw_smp_processor_id()); | |
| } | |
| 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, raw_smp_processor_id()); | |
| 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 = end - start; | |
| int node; | |
| dprintk(KERN_ERR "uncached_build_memmap(%lx %lx)\n", start, end); | |
| touch_softlockup_watchdog(); | |
| memset((char *)start, 0, length); | |
| node = paddr_to_nid(start - __IA64_UNCACHED_OFFSET); | |
| for (; start < end ; start += PAGE_SIZE) { | |
| dprintk(KERN_INFO "sticking %lx into the pool!\n", start); | |
| gen_pool_free(uncached_pool[node], start, 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); |