xref: /openbmc/linux/drivers/char/mspec.c (revision 25763b3c)
125763b3cSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
217a3b050SJes Sorensen /*
317a3b050SJes Sorensen  * Copyright (C) 2001-2006 Silicon Graphics, Inc.  All rights
417a3b050SJes Sorensen  * reserved.
517a3b050SJes Sorensen  */
617a3b050SJes Sorensen 
717a3b050SJes Sorensen /*
817a3b050SJes Sorensen  * SN Platform Special Memory (mspec) Support
917a3b050SJes Sorensen  *
1017a3b050SJes Sorensen  * This driver exports the SN special memory (mspec) facility to user
1117a3b050SJes Sorensen  * processes.
1217a3b050SJes Sorensen  * There are three types of memory made available thru this driver:
1317a3b050SJes Sorensen  * fetchops, uncached and cached.
1417a3b050SJes Sorensen  *
1517a3b050SJes Sorensen  * Fetchops are atomic memory operations that are implemented in the
1617a3b050SJes Sorensen  * memory controller on SGI SN hardware.
1717a3b050SJes Sorensen  *
1817a3b050SJes Sorensen  * Uncached are used for memory write combining feature of the ia64
1917a3b050SJes Sorensen  * cpu.
2017a3b050SJes Sorensen  *
2117a3b050SJes Sorensen  * Cached are used for areas of memory that are used as cached addresses
2217a3b050SJes Sorensen  * on our partition and used as uncached addresses from other partitions.
2317a3b050SJes Sorensen  * Due to a design constraint of the SN2 Shub, you can not have processors
2417a3b050SJes Sorensen  * on the same FSB perform both a cached and uncached reference to the
2517a3b050SJes Sorensen  * same cache line.  These special memory cached regions prevent the
2617a3b050SJes Sorensen  * kernel from ever dropping in a TLB entry and therefore prevent the
2717a3b050SJes Sorensen  * processor from ever speculating a cache line from this page.
2817a3b050SJes Sorensen  */
2917a3b050SJes Sorensen 
3017a3b050SJes Sorensen #include <linux/types.h>
3117a3b050SJes Sorensen #include <linux/kernel.h>
3217a3b050SJes Sorensen #include <linux/module.h>
3317a3b050SJes Sorensen #include <linux/init.h>
3417a3b050SJes Sorensen #include <linux/errno.h>
3517a3b050SJes Sorensen #include <linux/miscdevice.h>
3617a3b050SJes Sorensen #include <linux/spinlock.h>
3717a3b050SJes Sorensen #include <linux/mm.h>
384e950f6fSAlexey Dobriyan #include <linux/fs.h>
3917a3b050SJes Sorensen #include <linux/vmalloc.h>
4017a3b050SJes Sorensen #include <linux/string.h>
4117a3b050SJes Sorensen #include <linux/slab.h>
4217a3b050SJes Sorensen #include <linux/numa.h>
43f7d88d24SElena Reshetova #include <linux/refcount.h>
4417a3b050SJes Sorensen #include <asm/page.h>
4517a3b050SJes Sorensen #include <asm/pgtable.h>
4660063497SArun Sharma #include <linux/atomic.h>
4717a3b050SJes Sorensen #include <asm/tlbflush.h>
4817a3b050SJes Sorensen #include <asm/uncached.h>
4917a3b050SJes Sorensen #include <asm/sn/addrs.h>
5017a3b050SJes Sorensen #include <asm/sn/arch.h>
5117a3b050SJes Sorensen #include <asm/sn/mspec.h>
5217a3b050SJes Sorensen #include <asm/sn/sn_cpuid.h>
5317a3b050SJes Sorensen #include <asm/sn/io.h>
5417a3b050SJes Sorensen #include <asm/sn/bte.h>
5517a3b050SJes Sorensen #include <asm/sn/shubio.h>
5617a3b050SJes Sorensen 
5717a3b050SJes Sorensen 
5817a3b050SJes Sorensen #define FETCHOP_ID	"SGI Fetchop,"
5917a3b050SJes Sorensen #define CACHED_ID	"Cached,"
6017a3b050SJes Sorensen #define UNCACHED_ID	"Uncached"
6117a3b050SJes Sorensen #define REVISION	"4.0"
6217a3b050SJes Sorensen #define MSPEC_BASENAME	"mspec"
6317a3b050SJes Sorensen 
6417a3b050SJes Sorensen /*
6517a3b050SJes Sorensen  * Page types allocated by the device.
6617a3b050SJes Sorensen  */
674191ba26SCliff Wickman enum mspec_page_type {
6817a3b050SJes Sorensen 	MSPEC_FETCHOP = 1,
6917a3b050SJes Sorensen 	MSPEC_CACHED,
7017a3b050SJes Sorensen 	MSPEC_UNCACHED
7117a3b050SJes Sorensen };
7217a3b050SJes Sorensen 
731a4b0fc5SJes Sorensen #ifdef CONFIG_SGI_SN
7417a3b050SJes Sorensen static int is_sn2;
751a4b0fc5SJes Sorensen #else
761a4b0fc5SJes Sorensen #define is_sn2		0
771a4b0fc5SJes Sorensen #endif
7817a3b050SJes Sorensen 
7917a3b050SJes Sorensen /*
8017a3b050SJes Sorensen  * One of these structures is allocated when an mspec region is mmaped. The
8117a3b050SJes Sorensen  * structure is pointed to by the vma->vm_private_data field in the vma struct.
8217a3b050SJes Sorensen  * This structure is used to record the addresses of the mspec pages.
834191ba26SCliff Wickman  * This structure is shared by all vma's that are split off from the
844191ba26SCliff Wickman  * original vma when split_vma()'s are done.
854191ba26SCliff Wickman  *
864191ba26SCliff Wickman  * The refcnt is incremented atomically because mm->mmap_sem does not
874191ba26SCliff Wickman  * protect in fork case where multiple tasks share the vma_data.
8817a3b050SJes Sorensen  */
8917a3b050SJes Sorensen struct vma_data {
90f7d88d24SElena Reshetova 	refcount_t refcnt;	/* Number of vmas sharing the data. */
914191ba26SCliff Wickman 	spinlock_t lock;	/* Serialize access to this structure. */
9217a3b050SJes Sorensen 	int count;		/* Number of pages allocated. */
934191ba26SCliff Wickman 	enum mspec_page_type type; /* Type of pages allocated. */
944191ba26SCliff Wickman 	unsigned long vm_start;	/* Original (unsplit) base. */
954191ba26SCliff Wickman 	unsigned long vm_end;	/* Original (unsplit) end. */
9617a3b050SJes Sorensen 	unsigned long maddr[0];	/* Array of MSPEC addresses. */
9717a3b050SJes Sorensen };
9817a3b050SJes Sorensen 
9917a3b050SJes Sorensen /* used on shub2 to clear FOP cache in the HUB */
10017a3b050SJes Sorensen static unsigned long scratch_page[MAX_NUMNODES];
10117a3b050SJes Sorensen #define SH2_AMO_CACHE_ENTRIES	4
10217a3b050SJes Sorensen 
10317a3b050SJes Sorensen static inline int
10417a3b050SJes Sorensen mspec_zero_block(unsigned long addr, int len)
10517a3b050SJes Sorensen {
10617a3b050SJes Sorensen 	int status;
10717a3b050SJes Sorensen 
10817a3b050SJes Sorensen 	if (is_sn2) {
10917a3b050SJes Sorensen 		if (is_shub2()) {
11017a3b050SJes Sorensen 			int nid;
11117a3b050SJes Sorensen 			void *p;
11217a3b050SJes Sorensen 			int i;
11317a3b050SJes Sorensen 
11417a3b050SJes Sorensen 			nid = nasid_to_cnodeid(get_node_number(__pa(addr)));
11517a3b050SJes Sorensen 			p = (void *)TO_AMO(scratch_page[nid]);
11617a3b050SJes Sorensen 
11717a3b050SJes Sorensen 			for (i=0; i < SH2_AMO_CACHE_ENTRIES; i++) {
11817a3b050SJes Sorensen 				FETCHOP_LOAD_OP(p, FETCHOP_LOAD);
11917a3b050SJes Sorensen 				p += FETCHOP_VAR_SIZE;
12017a3b050SJes Sorensen 			}
12117a3b050SJes Sorensen 		}
12217a3b050SJes Sorensen 
12317a3b050SJes Sorensen 		status = bte_copy(0, addr & ~__IA64_UNCACHED_OFFSET, len,
12417a3b050SJes Sorensen 				  BTE_WACQUIRE | BTE_ZERO_FILL, NULL);
12517a3b050SJes Sorensen 	} else {
12617a3b050SJes Sorensen 		memset((char *) addr, 0, len);
12717a3b050SJes Sorensen 		status = 0;
12817a3b050SJes Sorensen 	}
12917a3b050SJes Sorensen 	return status;
13017a3b050SJes Sorensen }
13117a3b050SJes Sorensen 
13217a3b050SJes Sorensen /*
13317a3b050SJes Sorensen  * mspec_open
13417a3b050SJes Sorensen  *
13517a3b050SJes Sorensen  * Called when a device mapping is created by a means other than mmap
1364191ba26SCliff Wickman  * (via fork, munmap, etc.).  Increments the reference count on the
1374191ba26SCliff Wickman  * underlying mspec data so it is not freed prematurely.
13817a3b050SJes Sorensen  */
13917a3b050SJes Sorensen static void
14017a3b050SJes Sorensen mspec_open(struct vm_area_struct *vma)
14117a3b050SJes Sorensen {
14217a3b050SJes Sorensen 	struct vma_data *vdata;
14317a3b050SJes Sorensen 
14417a3b050SJes Sorensen 	vdata = vma->vm_private_data;
145f7d88d24SElena Reshetova 	refcount_inc(&vdata->refcnt);
14617a3b050SJes Sorensen }
14717a3b050SJes Sorensen 
14817a3b050SJes Sorensen /*
14917a3b050SJes Sorensen  * mspec_close
15017a3b050SJes Sorensen  *
15117a3b050SJes Sorensen  * Called when unmapping a device mapping. Frees all mspec pages
152afa684f6SCliff Wickman  * belonging to all the vma's sharing this vma_data structure.
15317a3b050SJes Sorensen  */
15417a3b050SJes Sorensen static void
15517a3b050SJes Sorensen mspec_close(struct vm_area_struct *vma)
15617a3b050SJes Sorensen {
15717a3b050SJes Sorensen 	struct vma_data *vdata;
158afa684f6SCliff Wickman 	int index, last_index;
1594191ba26SCliff Wickman 	unsigned long my_page;
16017a3b050SJes Sorensen 
16117a3b050SJes Sorensen 	vdata = vma->vm_private_data;
16217a3b050SJes Sorensen 
163f7d88d24SElena Reshetova 	if (!refcount_dec_and_test(&vdata->refcnt))
164afa684f6SCliff Wickman 		return;
1654191ba26SCliff Wickman 
166afa684f6SCliff Wickman 	last_index = (vdata->vm_end - vdata->vm_start) >> PAGE_SHIFT;
167afa684f6SCliff Wickman 	for (index = 0; index < last_index; index++) {
1684191ba26SCliff Wickman 		if (vdata->maddr[index] == 0)
16917a3b050SJes Sorensen 			continue;
17017a3b050SJes Sorensen 		/*
17117a3b050SJes Sorensen 		 * Clear the page before sticking it back
17217a3b050SJes Sorensen 		 * into the pool.
17317a3b050SJes Sorensen 		 */
1744191ba26SCliff Wickman 		my_page = vdata->maddr[index];
1754191ba26SCliff Wickman 		vdata->maddr[index] = 0;
176afa684f6SCliff Wickman 		if (!mspec_zero_block(my_page, PAGE_SIZE))
177e4a064dfSDean Nelson 			uncached_free_page(my_page, 1);
17817a3b050SJes Sorensen 		else
17917a3b050SJes Sorensen 			printk(KERN_WARNING "mspec_close(): "
180afa684f6SCliff Wickman 			       "failed to zero page %ld\n", my_page);
18117a3b050SJes Sorensen 	}
1824191ba26SCliff Wickman 
1831d5cfdb0STetsuo Handa 	kvfree(vdata);
18417a3b050SJes Sorensen }
18517a3b050SJes Sorensen 
18617a3b050SJes Sorensen /*
187efe9e779SNick Piggin  * mspec_fault
18817a3b050SJes Sorensen  *
18917a3b050SJes Sorensen  * Creates a mspec page and maps it to user space.
19017a3b050SJes Sorensen  */
1913eb87d4eSSouptick Joarder static vm_fault_t
19211bac800SDave Jiang mspec_fault(struct vm_fault *vmf)
19317a3b050SJes Sorensen {
19417a3b050SJes Sorensen 	unsigned long paddr, maddr;
19517a3b050SJes Sorensen 	unsigned long pfn;
196efe9e779SNick Piggin 	pgoff_t index = vmf->pgoff;
19711bac800SDave Jiang 	struct vma_data *vdata = vmf->vma->vm_private_data;
19817a3b050SJes Sorensen 
19917a3b050SJes Sorensen 	maddr = (volatile unsigned long) vdata->maddr[index];
20017a3b050SJes Sorensen 	if (maddr == 0) {
201e4a064dfSDean Nelson 		maddr = uncached_alloc_page(numa_node_id(), 1);
20217a3b050SJes Sorensen 		if (maddr == 0)
203efe9e779SNick Piggin 			return VM_FAULT_OOM;
20417a3b050SJes Sorensen 
20517a3b050SJes Sorensen 		spin_lock(&vdata->lock);
20617a3b050SJes Sorensen 		if (vdata->maddr[index] == 0) {
20717a3b050SJes Sorensen 			vdata->count++;
20817a3b050SJes Sorensen 			vdata->maddr[index] = maddr;
20917a3b050SJes Sorensen 		} else {
210e4a064dfSDean Nelson 			uncached_free_page(maddr, 1);
21117a3b050SJes Sorensen 			maddr = vdata->maddr[index];
21217a3b050SJes Sorensen 		}
21317a3b050SJes Sorensen 		spin_unlock(&vdata->lock);
21417a3b050SJes Sorensen 	}
21517a3b050SJes Sorensen 
21617a3b050SJes Sorensen 	if (vdata->type == MSPEC_FETCHOP)
21717a3b050SJes Sorensen 		paddr = TO_AMO(maddr);
21817a3b050SJes Sorensen 	else
2191a4b0fc5SJes Sorensen 		paddr = maddr & ~__IA64_UNCACHED_OFFSET;
22017a3b050SJes Sorensen 
22117a3b050SJes Sorensen 	pfn = paddr >> PAGE_SHIFT;
22217a3b050SJes Sorensen 
2233eb87d4eSSouptick Joarder 	return vmf_insert_pfn(vmf->vma, vmf->address, pfn);
22417a3b050SJes Sorensen }
22517a3b050SJes Sorensen 
226f0f37e2fSAlexey Dobriyan static const struct vm_operations_struct mspec_vm_ops = {
22717a3b050SJes Sorensen 	.open = mspec_open,
22817a3b050SJes Sorensen 	.close = mspec_close,
229efe9e779SNick Piggin 	.fault = mspec_fault,
23017a3b050SJes Sorensen };
23117a3b050SJes Sorensen 
23217a3b050SJes Sorensen /*
23317a3b050SJes Sorensen  * mspec_mmap
23417a3b050SJes Sorensen  *
235af901ca1SAndré Goddard Rosa  * Called when mmapping the device.  Initializes the vma with a fault handler
23617a3b050SJes Sorensen  * and private data structure necessary to allocate, track, and free the
23717a3b050SJes Sorensen  * underlying pages.
23817a3b050SJes Sorensen  */
23917a3b050SJes Sorensen static int
2404191ba26SCliff Wickman mspec_mmap(struct file *file, struct vm_area_struct *vma,
2414191ba26SCliff Wickman 					enum mspec_page_type type)
24217a3b050SJes Sorensen {
24317a3b050SJes Sorensen 	struct vma_data *vdata;
2441d5cfdb0STetsuo Handa 	int pages, vdata_size;
24517a3b050SJes Sorensen 
24617a3b050SJes Sorensen 	if (vma->vm_pgoff != 0)
24717a3b050SJes Sorensen 		return -EINVAL;
24817a3b050SJes Sorensen 
24917a3b050SJes Sorensen 	if ((vma->vm_flags & VM_SHARED) == 0)
25017a3b050SJes Sorensen 		return -EINVAL;
25117a3b050SJes Sorensen 
25217a3b050SJes Sorensen 	if ((vma->vm_flags & VM_WRITE) == 0)
25317a3b050SJes Sorensen 		return -EPERM;
25417a3b050SJes Sorensen 
255a0ea59d5SLibin 	pages = vma_pages(vma);
25617a3b050SJes Sorensen 	vdata_size = sizeof(struct vma_data) + pages * sizeof(long);
25717a3b050SJes Sorensen 	if (vdata_size <= PAGE_SIZE)
258658c74cfSRakib Mullick 		vdata = kzalloc(vdata_size, GFP_KERNEL);
2591d5cfdb0STetsuo Handa 	else
260658c74cfSRakib Mullick 		vdata = vzalloc(vdata_size);
26117a3b050SJes Sorensen 	if (!vdata)
26217a3b050SJes Sorensen 		return -ENOMEM;
26317a3b050SJes Sorensen 
2644191ba26SCliff Wickman 	vdata->vm_start = vma->vm_start;
2654191ba26SCliff Wickman 	vdata->vm_end = vma->vm_end;
26617a3b050SJes Sorensen 	vdata->type = type;
26717a3b050SJes Sorensen 	spin_lock_init(&vdata->lock);
268f7d88d24SElena Reshetova 	refcount_set(&vdata->refcnt, 1);
26917a3b050SJes Sorensen 	vma->vm_private_data = vdata;
27017a3b050SJes Sorensen 
271314e51b9SKonstantin Khlebnikov 	vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP;
27217a3b050SJes Sorensen 	if (vdata->type == MSPEC_FETCHOP || vdata->type == MSPEC_UNCACHED)
27317a3b050SJes Sorensen 		vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
27417a3b050SJes Sorensen 	vma->vm_ops = &mspec_vm_ops;
27517a3b050SJes Sorensen 
27617a3b050SJes Sorensen 	return 0;
27717a3b050SJes Sorensen }
27817a3b050SJes Sorensen 
27917a3b050SJes Sorensen static int
28017a3b050SJes Sorensen fetchop_mmap(struct file *file, struct vm_area_struct *vma)
28117a3b050SJes Sorensen {
28217a3b050SJes Sorensen 	return mspec_mmap(file, vma, MSPEC_FETCHOP);
28317a3b050SJes Sorensen }
28417a3b050SJes Sorensen 
28517a3b050SJes Sorensen static int
28617a3b050SJes Sorensen cached_mmap(struct file *file, struct vm_area_struct *vma)
28717a3b050SJes Sorensen {
28817a3b050SJes Sorensen 	return mspec_mmap(file, vma, MSPEC_CACHED);
28917a3b050SJes Sorensen }
29017a3b050SJes Sorensen 
29117a3b050SJes Sorensen static int
29217a3b050SJes Sorensen uncached_mmap(struct file *file, struct vm_area_struct *vma)
29317a3b050SJes Sorensen {
29417a3b050SJes Sorensen 	return mspec_mmap(file, vma, MSPEC_UNCACHED);
29517a3b050SJes Sorensen }
29617a3b050SJes Sorensen 
2972b8693c0SArjan van de Ven static const struct file_operations fetchop_fops = {
29817a3b050SJes Sorensen 	.owner = THIS_MODULE,
2996038f373SArnd Bergmann 	.mmap = fetchop_mmap,
3006038f373SArnd Bergmann 	.llseek = noop_llseek,
30117a3b050SJes Sorensen };
30217a3b050SJes Sorensen 
30317a3b050SJes Sorensen static struct miscdevice fetchop_miscdev = {
30417a3b050SJes Sorensen 	.minor = MISC_DYNAMIC_MINOR,
30517a3b050SJes Sorensen 	.name = "sgi_fetchop",
30617a3b050SJes Sorensen 	.fops = &fetchop_fops
30717a3b050SJes Sorensen };
30817a3b050SJes Sorensen 
3092b8693c0SArjan van de Ven static const struct file_operations cached_fops = {
31017a3b050SJes Sorensen 	.owner = THIS_MODULE,
3116038f373SArnd Bergmann 	.mmap = cached_mmap,
3126038f373SArnd Bergmann 	.llseek = noop_llseek,
31317a3b050SJes Sorensen };
31417a3b050SJes Sorensen 
31517a3b050SJes Sorensen static struct miscdevice cached_miscdev = {
31617a3b050SJes Sorensen 	.minor = MISC_DYNAMIC_MINOR,
31717a3b050SJes Sorensen 	.name = "mspec_cached",
31817a3b050SJes Sorensen 	.fops = &cached_fops
31917a3b050SJes Sorensen };
32017a3b050SJes Sorensen 
3212b8693c0SArjan van de Ven static const struct file_operations uncached_fops = {
32217a3b050SJes Sorensen 	.owner = THIS_MODULE,
3236038f373SArnd Bergmann 	.mmap = uncached_mmap,
3246038f373SArnd Bergmann 	.llseek = noop_llseek,
32517a3b050SJes Sorensen };
32617a3b050SJes Sorensen 
32717a3b050SJes Sorensen static struct miscdevice uncached_miscdev = {
32817a3b050SJes Sorensen 	.minor = MISC_DYNAMIC_MINOR,
32917a3b050SJes Sorensen 	.name = "mspec_uncached",
33017a3b050SJes Sorensen 	.fops = &uncached_fops
33117a3b050SJes Sorensen };
33217a3b050SJes Sorensen 
33317a3b050SJes Sorensen /*
33417a3b050SJes Sorensen  * mspec_init
33517a3b050SJes Sorensen  *
33617a3b050SJes Sorensen  * Called at boot time to initialize the mspec facility.
33717a3b050SJes Sorensen  */
33817a3b050SJes Sorensen static int __init
33917a3b050SJes Sorensen mspec_init(void)
34017a3b050SJes Sorensen {
34117a3b050SJes Sorensen 	int ret;
34217a3b050SJes Sorensen 	int nid;
34317a3b050SJes Sorensen 
34417a3b050SJes Sorensen 	/*
34517a3b050SJes Sorensen 	 * The fetchop device only works on SN2 hardware, uncached and cached
34617a3b050SJes Sorensen 	 * memory drivers should both be valid on all ia64 hardware
34717a3b050SJes Sorensen 	 */
3481a4b0fc5SJes Sorensen #ifdef CONFIG_SGI_SN
34917a3b050SJes Sorensen 	if (ia64_platform_is("sn2")) {
35017a3b050SJes Sorensen 		is_sn2 = 1;
35117a3b050SJes Sorensen 		if (is_shub2()) {
35217a3b050SJes Sorensen 			ret = -ENOMEM;
3532dca53a9SChristoph Lameter 			for_each_node_state(nid, N_ONLINE) {
35417a3b050SJes Sorensen 				int actual_nid;
35517a3b050SJes Sorensen 				int nasid;
35617a3b050SJes Sorensen 				unsigned long phys;
35717a3b050SJes Sorensen 
358e4a064dfSDean Nelson 				scratch_page[nid] = uncached_alloc_page(nid, 1);
35917a3b050SJes Sorensen 				if (scratch_page[nid] == 0)
36017a3b050SJes Sorensen 					goto free_scratch_pages;
36117a3b050SJes Sorensen 				phys = __pa(scratch_page[nid]);
36217a3b050SJes Sorensen 				nasid = get_node_number(phys);
36317a3b050SJes Sorensen 				actual_nid = nasid_to_cnodeid(nasid);
36417a3b050SJes Sorensen 				if (actual_nid != nid)
36517a3b050SJes Sorensen 					goto free_scratch_pages;
36617a3b050SJes Sorensen 			}
36717a3b050SJes Sorensen 		}
36817a3b050SJes Sorensen 
36917a3b050SJes Sorensen 		ret = misc_register(&fetchop_miscdev);
37017a3b050SJes Sorensen 		if (ret) {
37117a3b050SJes Sorensen 			printk(KERN_ERR
37217a3b050SJes Sorensen 			       "%s: failed to register device %i\n",
37317a3b050SJes Sorensen 			       FETCHOP_ID, ret);
37417a3b050SJes Sorensen 			goto free_scratch_pages;
37517a3b050SJes Sorensen 		}
37617a3b050SJes Sorensen 	}
3771a4b0fc5SJes Sorensen #endif
37817a3b050SJes Sorensen 	ret = misc_register(&cached_miscdev);
37917a3b050SJes Sorensen 	if (ret) {
38017a3b050SJes Sorensen 		printk(KERN_ERR "%s: failed to register device %i\n",
38117a3b050SJes Sorensen 		       CACHED_ID, ret);
38217a3b050SJes Sorensen 		if (is_sn2)
38317a3b050SJes Sorensen 			misc_deregister(&fetchop_miscdev);
38417a3b050SJes Sorensen 		goto free_scratch_pages;
38517a3b050SJes Sorensen 	}
38617a3b050SJes Sorensen 	ret = misc_register(&uncached_miscdev);
38717a3b050SJes Sorensen 	if (ret) {
38817a3b050SJes Sorensen 		printk(KERN_ERR "%s: failed to register device %i\n",
38917a3b050SJes Sorensen 		       UNCACHED_ID, ret);
39017a3b050SJes Sorensen 		misc_deregister(&cached_miscdev);
39117a3b050SJes Sorensen 		if (is_sn2)
39217a3b050SJes Sorensen 			misc_deregister(&fetchop_miscdev);
39317a3b050SJes Sorensen 		goto free_scratch_pages;
39417a3b050SJes Sorensen 	}
39517a3b050SJes Sorensen 
39617a3b050SJes Sorensen 	printk(KERN_INFO "%s %s initialized devices: %s %s %s\n",
39717a3b050SJes Sorensen 	       MSPEC_BASENAME, REVISION, is_sn2 ? FETCHOP_ID : "",
39817a3b050SJes Sorensen 	       CACHED_ID, UNCACHED_ID);
39917a3b050SJes Sorensen 
40017a3b050SJes Sorensen 	return 0;
40117a3b050SJes Sorensen 
40217a3b050SJes Sorensen  free_scratch_pages:
40317a3b050SJes Sorensen 	for_each_node(nid) {
40417a3b050SJes Sorensen 		if (scratch_page[nid] != 0)
405e4a064dfSDean Nelson 			uncached_free_page(scratch_page[nid], 1);
40617a3b050SJes Sorensen 	}
40717a3b050SJes Sorensen 	return ret;
40817a3b050SJes Sorensen }
40917a3b050SJes Sorensen 
41017a3b050SJes Sorensen static void __exit
41117a3b050SJes Sorensen mspec_exit(void)
41217a3b050SJes Sorensen {
41317a3b050SJes Sorensen 	int nid;
41417a3b050SJes Sorensen 
41517a3b050SJes Sorensen 	misc_deregister(&uncached_miscdev);
41617a3b050SJes Sorensen 	misc_deregister(&cached_miscdev);
41717a3b050SJes Sorensen 	if (is_sn2) {
41817a3b050SJes Sorensen 		misc_deregister(&fetchop_miscdev);
41917a3b050SJes Sorensen 
42017a3b050SJes Sorensen 		for_each_node(nid) {
42117a3b050SJes Sorensen 			if (scratch_page[nid] != 0)
422e4a064dfSDean Nelson 				uncached_free_page(scratch_page[nid], 1);
42317a3b050SJes Sorensen 		}
42417a3b050SJes Sorensen 	}
42517a3b050SJes Sorensen }
42617a3b050SJes Sorensen 
42717a3b050SJes Sorensen module_init(mspec_init);
42817a3b050SJes Sorensen module_exit(mspec_exit);
42917a3b050SJes Sorensen 
43017a3b050SJes Sorensen MODULE_AUTHOR("Silicon Graphics, Inc. <linux-altix@sgi.com>");
43117a3b050SJes Sorensen MODULE_DESCRIPTION("Driver for SGI SN special memory operations");
43217a3b050SJes Sorensen MODULE_LICENSE("GPL");
433