Thomas Gleixner | 25763b3 | 2019-05-28 10:10:09 -0700 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0-only |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 2 | /* |
| 3 | * Copyright (C) 2001-2006 Silicon Graphics, Inc. All rights |
| 4 | * reserved. |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 5 | */ |
| 6 | |
| 7 | /* |
| 8 | * SN Platform Special Memory (mspec) Support |
| 9 | * |
| 10 | * This driver exports the SN special memory (mspec) facility to user |
| 11 | * processes. |
Christoph Hellwig | 0fef253 | 2019-08-13 09:24:55 +0200 | [diff] [blame] | 12 | * There are two types of memory made available thru this driver: |
| 13 | * uncached and cached. |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 14 | * |
| 15 | * Uncached are used for memory write combining feature of the ia64 |
| 16 | * cpu. |
| 17 | * |
| 18 | * Cached are used for areas of memory that are used as cached addresses |
| 19 | * on our partition and used as uncached addresses from other partitions. |
| 20 | * Due to a design constraint of the SN2 Shub, you can not have processors |
| 21 | * on the same FSB perform both a cached and uncached reference to the |
| 22 | * same cache line. These special memory cached regions prevent the |
| 23 | * kernel from ever dropping in a TLB entry and therefore prevent the |
| 24 | * processor from ever speculating a cache line from this page. |
| 25 | */ |
| 26 | |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 27 | #include <linux/types.h> |
| 28 | #include <linux/kernel.h> |
| 29 | #include <linux/module.h> |
| 30 | #include <linux/init.h> |
| 31 | #include <linux/errno.h> |
| 32 | #include <linux/miscdevice.h> |
| 33 | #include <linux/spinlock.h> |
| 34 | #include <linux/mm.h> |
Alexey Dobriyan | 4e950f6 | 2007-07-30 02:36:13 +0400 | [diff] [blame] | 35 | #include <linux/fs.h> |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 36 | #include <linux/vmalloc.h> |
| 37 | #include <linux/string.h> |
| 38 | #include <linux/slab.h> |
| 39 | #include <linux/numa.h> |
Elena Reshetova | f7d88d2 | 2017-03-06 16:20:50 +0200 | [diff] [blame] | 40 | #include <linux/refcount.h> |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 41 | #include <asm/page.h> |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 42 | #include <asm/pgtable.h> |
Arun Sharma | 60063497 | 2011-07-26 16:09:06 -0700 | [diff] [blame] | 43 | #include <linux/atomic.h> |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 44 | #include <asm/tlbflush.h> |
| 45 | #include <asm/uncached.h> |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 46 | |
| 47 | |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 48 | #define CACHED_ID "Cached," |
| 49 | #define UNCACHED_ID "Uncached" |
| 50 | #define REVISION "4.0" |
| 51 | #define MSPEC_BASENAME "mspec" |
| 52 | |
| 53 | /* |
| 54 | * Page types allocated by the device. |
| 55 | */ |
Cliff Wickman | 4191ba2 | 2007-09-18 22:46:31 -0700 | [diff] [blame] | 56 | enum mspec_page_type { |
Christoph Hellwig | 0fef253 | 2019-08-13 09:24:55 +0200 | [diff] [blame] | 57 | MSPEC_CACHED = 2, |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 58 | MSPEC_UNCACHED |
| 59 | }; |
| 60 | |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 61 | /* |
| 62 | * One of these structures is allocated when an mspec region is mmaped. The |
| 63 | * structure is pointed to by the vma->vm_private_data field in the vma struct. |
| 64 | * This structure is used to record the addresses of the mspec pages. |
Cliff Wickman | 4191ba2 | 2007-09-18 22:46:31 -0700 | [diff] [blame] | 65 | * This structure is shared by all vma's that are split off from the |
| 66 | * original vma when split_vma()'s are done. |
| 67 | * |
| 68 | * The refcnt is incremented atomically because mm->mmap_sem does not |
| 69 | * protect in fork case where multiple tasks share the vma_data. |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 70 | */ |
| 71 | struct vma_data { |
Elena Reshetova | f7d88d2 | 2017-03-06 16:20:50 +0200 | [diff] [blame] | 72 | refcount_t refcnt; /* Number of vmas sharing the data. */ |
Cliff Wickman | 4191ba2 | 2007-09-18 22:46:31 -0700 | [diff] [blame] | 73 | spinlock_t lock; /* Serialize access to this structure. */ |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 74 | int count; /* Number of pages allocated. */ |
Cliff Wickman | 4191ba2 | 2007-09-18 22:46:31 -0700 | [diff] [blame] | 75 | enum mspec_page_type type; /* Type of pages allocated. */ |
Cliff Wickman | 4191ba2 | 2007-09-18 22:46:31 -0700 | [diff] [blame] | 76 | unsigned long vm_start; /* Original (unsplit) base. */ |
| 77 | unsigned long vm_end; /* Original (unsplit) end. */ |
Gustavo A. R. Silva | 3c2faf6 | 2020-02-27 12:48:08 -0600 | [diff] [blame^] | 78 | unsigned long maddr[]; /* Array of MSPEC addresses. */ |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 79 | }; |
| 80 | |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 81 | /* |
| 82 | * mspec_open |
| 83 | * |
| 84 | * Called when a device mapping is created by a means other than mmap |
Cliff Wickman | 4191ba2 | 2007-09-18 22:46:31 -0700 | [diff] [blame] | 85 | * (via fork, munmap, etc.). Increments the reference count on the |
| 86 | * underlying mspec data so it is not freed prematurely. |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 87 | */ |
| 88 | static void |
| 89 | mspec_open(struct vm_area_struct *vma) |
| 90 | { |
| 91 | struct vma_data *vdata; |
| 92 | |
| 93 | vdata = vma->vm_private_data; |
Elena Reshetova | f7d88d2 | 2017-03-06 16:20:50 +0200 | [diff] [blame] | 94 | refcount_inc(&vdata->refcnt); |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 95 | } |
| 96 | |
| 97 | /* |
| 98 | * mspec_close |
| 99 | * |
| 100 | * Called when unmapping a device mapping. Frees all mspec pages |
Cliff Wickman | afa684f | 2007-09-24 21:24:41 -0700 | [diff] [blame] | 101 | * belonging to all the vma's sharing this vma_data structure. |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 102 | */ |
| 103 | static void |
| 104 | mspec_close(struct vm_area_struct *vma) |
| 105 | { |
| 106 | struct vma_data *vdata; |
Cliff Wickman | afa684f | 2007-09-24 21:24:41 -0700 | [diff] [blame] | 107 | int index, last_index; |
Cliff Wickman | 4191ba2 | 2007-09-18 22:46:31 -0700 | [diff] [blame] | 108 | unsigned long my_page; |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 109 | |
| 110 | vdata = vma->vm_private_data; |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 111 | |
Elena Reshetova | f7d88d2 | 2017-03-06 16:20:50 +0200 | [diff] [blame] | 112 | if (!refcount_dec_and_test(&vdata->refcnt)) |
Cliff Wickman | afa684f | 2007-09-24 21:24:41 -0700 | [diff] [blame] | 113 | return; |
Cliff Wickman | 4191ba2 | 2007-09-18 22:46:31 -0700 | [diff] [blame] | 114 | |
Cliff Wickman | afa684f | 2007-09-24 21:24:41 -0700 | [diff] [blame] | 115 | last_index = (vdata->vm_end - vdata->vm_start) >> PAGE_SHIFT; |
| 116 | for (index = 0; index < last_index; index++) { |
Cliff Wickman | 4191ba2 | 2007-09-18 22:46:31 -0700 | [diff] [blame] | 117 | if (vdata->maddr[index] == 0) |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 118 | continue; |
| 119 | /* |
| 120 | * Clear the page before sticking it back |
| 121 | * into the pool. |
| 122 | */ |
Cliff Wickman | 4191ba2 | 2007-09-18 22:46:31 -0700 | [diff] [blame] | 123 | my_page = vdata->maddr[index]; |
| 124 | vdata->maddr[index] = 0; |
Christoph Hellwig | 0fef253 | 2019-08-13 09:24:55 +0200 | [diff] [blame] | 125 | memset((char *)my_page, 0, PAGE_SIZE); |
| 126 | uncached_free_page(my_page, 1); |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 127 | } |
Cliff Wickman | 4191ba2 | 2007-09-18 22:46:31 -0700 | [diff] [blame] | 128 | |
Tetsuo Handa | 1d5cfdb | 2016-01-22 15:11:02 -0800 | [diff] [blame] | 129 | kvfree(vdata); |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 130 | } |
| 131 | |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 132 | /* |
Nick Piggin | efe9e77 | 2008-07-23 21:27:00 -0700 | [diff] [blame] | 133 | * mspec_fault |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 134 | * |
| 135 | * Creates a mspec page and maps it to user space. |
| 136 | */ |
Souptick Joarder | 3eb87d4 | 2018-04-16 19:56:09 +0530 | [diff] [blame] | 137 | static vm_fault_t |
Dave Jiang | 11bac80 | 2017-02-24 14:56:41 -0800 | [diff] [blame] | 138 | mspec_fault(struct vm_fault *vmf) |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 139 | { |
| 140 | unsigned long paddr, maddr; |
| 141 | unsigned long pfn; |
Nick Piggin | efe9e77 | 2008-07-23 21:27:00 -0700 | [diff] [blame] | 142 | pgoff_t index = vmf->pgoff; |
Dave Jiang | 11bac80 | 2017-02-24 14:56:41 -0800 | [diff] [blame] | 143 | struct vma_data *vdata = vmf->vma->vm_private_data; |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 144 | |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 145 | maddr = (volatile unsigned long) vdata->maddr[index]; |
| 146 | if (maddr == 0) { |
Dean Nelson | e4a064d | 2008-04-25 15:22:19 -0500 | [diff] [blame] | 147 | maddr = uncached_alloc_page(numa_node_id(), 1); |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 148 | if (maddr == 0) |
Nick Piggin | efe9e77 | 2008-07-23 21:27:00 -0700 | [diff] [blame] | 149 | return VM_FAULT_OOM; |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 150 | |
| 151 | spin_lock(&vdata->lock); |
| 152 | if (vdata->maddr[index] == 0) { |
| 153 | vdata->count++; |
| 154 | vdata->maddr[index] = maddr; |
| 155 | } else { |
Dean Nelson | e4a064d | 2008-04-25 15:22:19 -0500 | [diff] [blame] | 156 | uncached_free_page(maddr, 1); |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 157 | maddr = vdata->maddr[index]; |
| 158 | } |
| 159 | spin_unlock(&vdata->lock); |
| 160 | } |
| 161 | |
Christoph Hellwig | 0fef253 | 2019-08-13 09:24:55 +0200 | [diff] [blame] | 162 | paddr = maddr & ~__IA64_UNCACHED_OFFSET; |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 163 | pfn = paddr >> PAGE_SHIFT; |
| 164 | |
Souptick Joarder | 3eb87d4 | 2018-04-16 19:56:09 +0530 | [diff] [blame] | 165 | return vmf_insert_pfn(vmf->vma, vmf->address, pfn); |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 166 | } |
| 167 | |
Alexey Dobriyan | f0f37e2f | 2009-09-27 22:29:37 +0400 | [diff] [blame] | 168 | static const struct vm_operations_struct mspec_vm_ops = { |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 169 | .open = mspec_open, |
| 170 | .close = mspec_close, |
Nick Piggin | efe9e77 | 2008-07-23 21:27:00 -0700 | [diff] [blame] | 171 | .fault = mspec_fault, |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 172 | }; |
| 173 | |
| 174 | /* |
| 175 | * mspec_mmap |
| 176 | * |
André Goddard Rosa | af901ca | 2009-11-14 13:09:05 -0200 | [diff] [blame] | 177 | * Called when mmapping the device. Initializes the vma with a fault handler |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 178 | * and private data structure necessary to allocate, track, and free the |
| 179 | * underlying pages. |
| 180 | */ |
| 181 | static int |
Cliff Wickman | 4191ba2 | 2007-09-18 22:46:31 -0700 | [diff] [blame] | 182 | mspec_mmap(struct file *file, struct vm_area_struct *vma, |
| 183 | enum mspec_page_type type) |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 184 | { |
| 185 | struct vma_data *vdata; |
Tetsuo Handa | 1d5cfdb | 2016-01-22 15:11:02 -0800 | [diff] [blame] | 186 | int pages, vdata_size; |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 187 | |
| 188 | if (vma->vm_pgoff != 0) |
| 189 | return -EINVAL; |
| 190 | |
| 191 | if ((vma->vm_flags & VM_SHARED) == 0) |
| 192 | return -EINVAL; |
| 193 | |
| 194 | if ((vma->vm_flags & VM_WRITE) == 0) |
| 195 | return -EPERM; |
| 196 | |
Libin | a0ea59d | 2013-05-13 10:17:39 +0800 | [diff] [blame] | 197 | pages = vma_pages(vma); |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 198 | vdata_size = sizeof(struct vma_data) + pages * sizeof(long); |
| 199 | if (vdata_size <= PAGE_SIZE) |
Rakib Mullick | 658c74c | 2011-05-26 16:25:56 -0700 | [diff] [blame] | 200 | vdata = kzalloc(vdata_size, GFP_KERNEL); |
Tetsuo Handa | 1d5cfdb | 2016-01-22 15:11:02 -0800 | [diff] [blame] | 201 | else |
Rakib Mullick | 658c74c | 2011-05-26 16:25:56 -0700 | [diff] [blame] | 202 | vdata = vzalloc(vdata_size); |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 203 | if (!vdata) |
| 204 | return -ENOMEM; |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 205 | |
Cliff Wickman | 4191ba2 | 2007-09-18 22:46:31 -0700 | [diff] [blame] | 206 | vdata->vm_start = vma->vm_start; |
| 207 | vdata->vm_end = vma->vm_end; |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 208 | vdata->type = type; |
| 209 | spin_lock_init(&vdata->lock); |
Elena Reshetova | f7d88d2 | 2017-03-06 16:20:50 +0200 | [diff] [blame] | 210 | refcount_set(&vdata->refcnt, 1); |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 211 | vma->vm_private_data = vdata; |
| 212 | |
Konstantin Khlebnikov | 314e51b | 2012-10-08 16:29:02 -0700 | [diff] [blame] | 213 | vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP; |
Christoph Hellwig | 0fef253 | 2019-08-13 09:24:55 +0200 | [diff] [blame] | 214 | if (vdata->type == MSPEC_UNCACHED) |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 215 | vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); |
| 216 | vma->vm_ops = &mspec_vm_ops; |
| 217 | |
| 218 | return 0; |
| 219 | } |
| 220 | |
| 221 | static int |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 222 | cached_mmap(struct file *file, struct vm_area_struct *vma) |
| 223 | { |
| 224 | return mspec_mmap(file, vma, MSPEC_CACHED); |
| 225 | } |
| 226 | |
| 227 | static int |
| 228 | uncached_mmap(struct file *file, struct vm_area_struct *vma) |
| 229 | { |
| 230 | return mspec_mmap(file, vma, MSPEC_UNCACHED); |
| 231 | } |
| 232 | |
Arjan van de Ven | 2b8693c | 2007-02-12 00:55:32 -0800 | [diff] [blame] | 233 | static const struct file_operations cached_fops = { |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 234 | .owner = THIS_MODULE, |
Arnd Bergmann | 6038f37 | 2010-08-15 18:52:59 +0200 | [diff] [blame] | 235 | .mmap = cached_mmap, |
| 236 | .llseek = noop_llseek, |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 237 | }; |
| 238 | |
| 239 | static struct miscdevice cached_miscdev = { |
| 240 | .minor = MISC_DYNAMIC_MINOR, |
| 241 | .name = "mspec_cached", |
| 242 | .fops = &cached_fops |
| 243 | }; |
| 244 | |
Arjan van de Ven | 2b8693c | 2007-02-12 00:55:32 -0800 | [diff] [blame] | 245 | static const struct file_operations uncached_fops = { |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 246 | .owner = THIS_MODULE, |
Arnd Bergmann | 6038f37 | 2010-08-15 18:52:59 +0200 | [diff] [blame] | 247 | .mmap = uncached_mmap, |
| 248 | .llseek = noop_llseek, |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 249 | }; |
| 250 | |
| 251 | static struct miscdevice uncached_miscdev = { |
| 252 | .minor = MISC_DYNAMIC_MINOR, |
| 253 | .name = "mspec_uncached", |
| 254 | .fops = &uncached_fops |
| 255 | }; |
| 256 | |
| 257 | /* |
| 258 | * mspec_init |
| 259 | * |
| 260 | * Called at boot time to initialize the mspec facility. |
| 261 | */ |
| 262 | static int __init |
| 263 | mspec_init(void) |
| 264 | { |
| 265 | int ret; |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 266 | |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 267 | ret = misc_register(&cached_miscdev); |
| 268 | if (ret) { |
| 269 | printk(KERN_ERR "%s: failed to register device %i\n", |
| 270 | CACHED_ID, ret); |
Christoph Hellwig | 0fef253 | 2019-08-13 09:24:55 +0200 | [diff] [blame] | 271 | return ret; |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 272 | } |
| 273 | ret = misc_register(&uncached_miscdev); |
| 274 | if (ret) { |
| 275 | printk(KERN_ERR "%s: failed to register device %i\n", |
| 276 | UNCACHED_ID, ret); |
| 277 | misc_deregister(&cached_miscdev); |
Christoph Hellwig | 0fef253 | 2019-08-13 09:24:55 +0200 | [diff] [blame] | 278 | return ret; |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 279 | } |
| 280 | |
Christoph Hellwig | 0fef253 | 2019-08-13 09:24:55 +0200 | [diff] [blame] | 281 | printk(KERN_INFO "%s %s initialized devices: %s %s\n", |
| 282 | MSPEC_BASENAME, REVISION, CACHED_ID, UNCACHED_ID); |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 283 | |
| 284 | return 0; |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 285 | } |
| 286 | |
| 287 | static void __exit |
| 288 | mspec_exit(void) |
| 289 | { |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 290 | misc_deregister(&uncached_miscdev); |
| 291 | misc_deregister(&cached_miscdev); |
Jes Sorensen | 17a3b05 | 2006-09-27 01:50:11 -0700 | [diff] [blame] | 292 | } |
| 293 | |
| 294 | module_init(mspec_init); |
| 295 | module_exit(mspec_exit); |
| 296 | |
| 297 | MODULE_AUTHOR("Silicon Graphics, Inc. <linux-altix@sgi.com>"); |
| 298 | MODULE_DESCRIPTION("Driver for SGI SN special memory operations"); |
| 299 | MODULE_LICENSE("GPL"); |