Commit | Line | Data |
---|---|---|
25763b3c | 1 | // SPDX-License-Identifier: GPL-2.0-only |
17a3b050 JS |
2 | /* |
3 | * Copyright (C) 2001-2006 Silicon Graphics, Inc. All rights | |
4 | * reserved. | |
17a3b050 JS |
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. | |
0fef2532 CH |
12 | * There are two types of memory made available thru this driver: |
13 | * uncached and cached. | |
17a3b050 JS |
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 | ||
17a3b050 JS |
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> | |
4e950f6f | 35 | #include <linux/fs.h> |
17a3b050 JS |
36 | #include <linux/vmalloc.h> |
37 | #include <linux/string.h> | |
38 | #include <linux/slab.h> | |
39 | #include <linux/numa.h> | |
f7d88d24 | 40 | #include <linux/refcount.h> |
17a3b050 | 41 | #include <asm/page.h> |
17a3b050 | 42 | #include <asm/pgtable.h> |
60063497 | 43 | #include <linux/atomic.h> |
17a3b050 JS |
44 | #include <asm/tlbflush.h> |
45 | #include <asm/uncached.h> | |
17a3b050 JS |
46 | |
47 | ||
17a3b050 JS |
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 | */ | |
4191ba26 | 56 | enum mspec_page_type { |
0fef2532 | 57 | MSPEC_CACHED = 2, |
17a3b050 JS |
58 | MSPEC_UNCACHED |
59 | }; | |
60 | ||
17a3b050 JS |
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. | |
4191ba26 CW |
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. | |
17a3b050 JS |
70 | */ |
71 | struct vma_data { | |
f7d88d24 | 72 | refcount_t refcnt; /* Number of vmas sharing the data. */ |
4191ba26 | 73 | spinlock_t lock; /* Serialize access to this structure. */ |
17a3b050 | 74 | int count; /* Number of pages allocated. */ |
4191ba26 | 75 | enum mspec_page_type type; /* Type of pages allocated. */ |
4191ba26 CW |
76 | unsigned long vm_start; /* Original (unsplit) base. */ |
77 | unsigned long vm_end; /* Original (unsplit) end. */ | |
17a3b050 JS |
78 | unsigned long maddr[0]; /* Array of MSPEC addresses. */ |
79 | }; | |
80 | ||
17a3b050 JS |
81 | /* |
82 | * mspec_open | |
83 | * | |
84 | * Called when a device mapping is created by a means other than mmap | |
4191ba26 CW |
85 | * (via fork, munmap, etc.). Increments the reference count on the |
86 | * underlying mspec data so it is not freed prematurely. | |
17a3b050 JS |
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; | |
f7d88d24 | 94 | refcount_inc(&vdata->refcnt); |
17a3b050 JS |
95 | } |
96 | ||
97 | /* | |
98 | * mspec_close | |
99 | * | |
100 | * Called when unmapping a device mapping. Frees all mspec pages | |
afa684f6 | 101 | * belonging to all the vma's sharing this vma_data structure. |
17a3b050 JS |
102 | */ |
103 | static void | |
104 | mspec_close(struct vm_area_struct *vma) | |
105 | { | |
106 | struct vma_data *vdata; | |
afa684f6 | 107 | int index, last_index; |
4191ba26 | 108 | unsigned long my_page; |
17a3b050 JS |
109 | |
110 | vdata = vma->vm_private_data; | |
17a3b050 | 111 | |
f7d88d24 | 112 | if (!refcount_dec_and_test(&vdata->refcnt)) |
afa684f6 | 113 | return; |
4191ba26 | 114 | |
afa684f6 CW |
115 | last_index = (vdata->vm_end - vdata->vm_start) >> PAGE_SHIFT; |
116 | for (index = 0; index < last_index; index++) { | |
4191ba26 | 117 | if (vdata->maddr[index] == 0) |
17a3b050 JS |
118 | continue; |
119 | /* | |
120 | * Clear the page before sticking it back | |
121 | * into the pool. | |
122 | */ | |
4191ba26 CW |
123 | my_page = vdata->maddr[index]; |
124 | vdata->maddr[index] = 0; | |
0fef2532 CH |
125 | memset((char *)my_page, 0, PAGE_SIZE); |
126 | uncached_free_page(my_page, 1); | |
17a3b050 | 127 | } |
4191ba26 | 128 | |
1d5cfdb0 | 129 | kvfree(vdata); |
17a3b050 JS |
130 | } |
131 | ||
17a3b050 | 132 | /* |
efe9e779 | 133 | * mspec_fault |
17a3b050 JS |
134 | * |
135 | * Creates a mspec page and maps it to user space. | |
136 | */ | |
3eb87d4e | 137 | static vm_fault_t |
11bac800 | 138 | mspec_fault(struct vm_fault *vmf) |
17a3b050 JS |
139 | { |
140 | unsigned long paddr, maddr; | |
141 | unsigned long pfn; | |
efe9e779 | 142 | pgoff_t index = vmf->pgoff; |
11bac800 | 143 | struct vma_data *vdata = vmf->vma->vm_private_data; |
17a3b050 | 144 | |
17a3b050 JS |
145 | maddr = (volatile unsigned long) vdata->maddr[index]; |
146 | if (maddr == 0) { | |
e4a064df | 147 | maddr = uncached_alloc_page(numa_node_id(), 1); |
17a3b050 | 148 | if (maddr == 0) |
efe9e779 | 149 | return VM_FAULT_OOM; |
17a3b050 JS |
150 | |
151 | spin_lock(&vdata->lock); | |
152 | if (vdata->maddr[index] == 0) { | |
153 | vdata->count++; | |
154 | vdata->maddr[index] = maddr; | |
155 | } else { | |
e4a064df | 156 | uncached_free_page(maddr, 1); |
17a3b050 JS |
157 | maddr = vdata->maddr[index]; |
158 | } | |
159 | spin_unlock(&vdata->lock); | |
160 | } | |
161 | ||
0fef2532 | 162 | paddr = maddr & ~__IA64_UNCACHED_OFFSET; |
17a3b050 JS |
163 | pfn = paddr >> PAGE_SHIFT; |
164 | ||
3eb87d4e | 165 | return vmf_insert_pfn(vmf->vma, vmf->address, pfn); |
17a3b050 JS |
166 | } |
167 | ||
f0f37e2f | 168 | static const struct vm_operations_struct mspec_vm_ops = { |
17a3b050 JS |
169 | .open = mspec_open, |
170 | .close = mspec_close, | |
efe9e779 | 171 | .fault = mspec_fault, |
17a3b050 JS |
172 | }; |
173 | ||
174 | /* | |
175 | * mspec_mmap | |
176 | * | |
af901ca1 | 177 | * Called when mmapping the device. Initializes the vma with a fault handler |
17a3b050 JS |
178 | * and private data structure necessary to allocate, track, and free the |
179 | * underlying pages. | |
180 | */ | |
181 | static int | |
4191ba26 CW |
182 | mspec_mmap(struct file *file, struct vm_area_struct *vma, |
183 | enum mspec_page_type type) | |
17a3b050 JS |
184 | { |
185 | struct vma_data *vdata; | |
1d5cfdb0 | 186 | int pages, vdata_size; |
17a3b050 JS |
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 | ||
a0ea59d5 | 197 | pages = vma_pages(vma); |
17a3b050 JS |
198 | vdata_size = sizeof(struct vma_data) + pages * sizeof(long); |
199 | if (vdata_size <= PAGE_SIZE) | |
658c74cf | 200 | vdata = kzalloc(vdata_size, GFP_KERNEL); |
1d5cfdb0 | 201 | else |
658c74cf | 202 | vdata = vzalloc(vdata_size); |
17a3b050 JS |
203 | if (!vdata) |
204 | return -ENOMEM; | |
17a3b050 | 205 | |
4191ba26 CW |
206 | vdata->vm_start = vma->vm_start; |
207 | vdata->vm_end = vma->vm_end; | |
17a3b050 JS |
208 | vdata->type = type; |
209 | spin_lock_init(&vdata->lock); | |
f7d88d24 | 210 | refcount_set(&vdata->refcnt, 1); |
17a3b050 JS |
211 | vma->vm_private_data = vdata; |
212 | ||
314e51b9 | 213 | vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP; |
0fef2532 | 214 | if (vdata->type == MSPEC_UNCACHED) |
17a3b050 JS |
215 | vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); |
216 | vma->vm_ops = &mspec_vm_ops; | |
217 | ||
218 | return 0; | |
219 | } | |
220 | ||
17a3b050 JS |
221 | static int |
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 | ||
2b8693c0 | 233 | static const struct file_operations cached_fops = { |
17a3b050 | 234 | .owner = THIS_MODULE, |
6038f373 AB |
235 | .mmap = cached_mmap, |
236 | .llseek = noop_llseek, | |
17a3b050 JS |
237 | }; |
238 | ||
239 | static struct miscdevice cached_miscdev = { | |
240 | .minor = MISC_DYNAMIC_MINOR, | |
241 | .name = "mspec_cached", | |
242 | .fops = &cached_fops | |
243 | }; | |
244 | ||
2b8693c0 | 245 | static const struct file_operations uncached_fops = { |
17a3b050 | 246 | .owner = THIS_MODULE, |
6038f373 AB |
247 | .mmap = uncached_mmap, |
248 | .llseek = noop_llseek, | |
17a3b050 JS |
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; | |
17a3b050 | 266 | |
17a3b050 JS |
267 | ret = misc_register(&cached_miscdev); |
268 | if (ret) { | |
269 | printk(KERN_ERR "%s: failed to register device %i\n", | |
270 | CACHED_ID, ret); | |
0fef2532 | 271 | return ret; |
17a3b050 JS |
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); | |
0fef2532 | 278 | return ret; |
17a3b050 JS |
279 | } |
280 | ||
0fef2532 CH |
281 | printk(KERN_INFO "%s %s initialized devices: %s %s\n", |
282 | MSPEC_BASENAME, REVISION, CACHED_ID, UNCACHED_ID); | |
17a3b050 JS |
283 | |
284 | return 0; | |
17a3b050 JS |
285 | } |
286 | ||
287 | static void __exit | |
288 | mspec_exit(void) | |
289 | { | |
17a3b050 JS |
290 | misc_deregister(&uncached_miscdev); |
291 | misc_deregister(&cached_miscdev); | |
17a3b050 JS |
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"); |