]>
Commit | Line | Data |
---|---|---|
1da177e4 | 1 | /* |
d7c30c68 | 2 | * arch/sh/kernel/cpu/sh4/sq.c |
1da177e4 LT |
3 | * |
4 | * General management API for SH-4 integrated Store Queues | |
5 | * | |
d7c30c68 | 6 | * Copyright (C) 2001 - 2006 Paul Mundt |
1da177e4 LT |
7 | * Copyright (C) 2001, 2002 M. R. Brown |
8 | * | |
1da177e4 LT |
9 | * This file is subject to the terms and conditions of the GNU General Public |
10 | * License. See the file "COPYING" in the main directory of this archive | |
11 | * for more details. | |
12 | */ | |
13 | #include <linux/init.h> | |
d7c30c68 PM |
14 | #include <linux/cpu.h> |
15 | #include <linux/bitmap.h> | |
16 | #include <linux/sysdev.h> | |
1da177e4 LT |
17 | #include <linux/kernel.h> |
18 | #include <linux/module.h> | |
1da177e4 | 19 | #include <linux/slab.h> |
1da177e4 | 20 | #include <linux/vmalloc.h> |
e4c2cfee | 21 | #include <linux/mm.h> |
9f650cf2 | 22 | #include <linux/io.h> |
1da177e4 | 23 | #include <asm/page.h> |
e4c2cfee | 24 | #include <asm/cacheflush.h> |
1da177e4 LT |
25 | #include <asm/cpu/sq.h> |
26 | ||
d7c30c68 PM |
27 | struct sq_mapping; |
28 | ||
29 | struct sq_mapping { | |
30 | const char *name; | |
31 | ||
32 | unsigned long sq_addr; | |
33 | unsigned long addr; | |
34 | unsigned int size; | |
35 | ||
36 | struct sq_mapping *next; | |
37 | }; | |
38 | ||
39 | static struct sq_mapping *sq_mapping_list; | |
1da177e4 | 40 | static DEFINE_SPINLOCK(sq_mapping_lock); |
e18b890b | 41 | static struct kmem_cache *sq_cache; |
d7c30c68 | 42 | static unsigned long *sq_bitmap; |
1da177e4 | 43 | |
d7c30c68 PM |
44 | #define store_queue_barrier() \ |
45 | do { \ | |
46 | (void)ctrl_inl(P4SEG_STORE_QUE); \ | |
47 | ctrl_outl(0, P4SEG_STORE_QUE + 0); \ | |
48 | ctrl_outl(0, P4SEG_STORE_QUE + 8); \ | |
49 | } while (0); | |
1da177e4 LT |
50 | |
51 | /** | |
52 | * sq_flush_range - Flush (prefetch) a specific SQ range | |
53 | * @start: the store queue address to start flushing from | |
54 | * @len: the length to flush | |
55 | * | |
56 | * Flushes the store queue cache from @start to @start + @len in a | |
57 | * linear fashion. | |
58 | */ | |
59 | void sq_flush_range(unsigned long start, unsigned int len) | |
60 | { | |
b05d1865 | 61 | unsigned long *sq = (unsigned long *)start; |
1da177e4 LT |
62 | |
63 | /* Flush the queues */ | |
64 | for (len >>= 5; len--; sq += 8) | |
b05d1865 | 65 | prefetchw(sq); |
1da177e4 LT |
66 | |
67 | /* Wait for completion */ | |
d7c30c68 | 68 | store_queue_barrier(); |
1da177e4 | 69 | } |
9f650cf2 | 70 | EXPORT_SYMBOL(sq_flush_range); |
1da177e4 | 71 | |
d7c30c68 | 72 | static inline void sq_mapping_list_add(struct sq_mapping *map) |
1da177e4 | 73 | { |
d7c30c68 | 74 | struct sq_mapping **p, *tmp; |
1da177e4 | 75 | |
d7c30c68 | 76 | spin_lock_irq(&sq_mapping_lock); |
1da177e4 | 77 | |
d7c30c68 PM |
78 | p = &sq_mapping_list; |
79 | while ((tmp = *p) != NULL) | |
80 | p = &tmp->next; | |
1da177e4 | 81 | |
d7c30c68 PM |
82 | map->next = tmp; |
83 | *p = map; | |
1da177e4 | 84 | |
d7c30c68 | 85 | spin_unlock_irq(&sq_mapping_lock); |
1da177e4 LT |
86 | } |
87 | ||
d7c30c68 | 88 | static inline void sq_mapping_list_del(struct sq_mapping *map) |
1da177e4 | 89 | { |
d7c30c68 PM |
90 | struct sq_mapping **p, *tmp; |
91 | ||
92 | spin_lock_irq(&sq_mapping_lock); | |
93 | ||
94 | for (p = &sq_mapping_list; (tmp = *p); p = &tmp->next) | |
95 | if (tmp == map) { | |
96 | *p = tmp->next; | |
97 | break; | |
1da177e4 | 98 | } |
1da177e4 | 99 | |
d7c30c68 | 100 | spin_unlock_irq(&sq_mapping_lock); |
1da177e4 LT |
101 | } |
102 | ||
d7c30c68 | 103 | static int __sq_remap(struct sq_mapping *map, unsigned long flags) |
1da177e4 | 104 | { |
d7c30c68 | 105 | #if defined(CONFIG_MMU) |
1da177e4 | 106 | struct vm_struct *vma; |
1da177e4 | 107 | |
1da177e4 LT |
108 | vma = __get_vm_area(map->size, VM_ALLOC, map->sq_addr, SQ_ADDRMAX); |
109 | if (!vma) | |
d7c30c68 | 110 | return -ENOMEM; |
1da177e4 LT |
111 | |
112 | vma->phys_addr = map->addr; | |
113 | ||
37bda1da PM |
114 | if (ioremap_page_range((unsigned long)vma->addr, |
115 | (unsigned long)vma->addr + map->size, | |
116 | vma->phys_addr, __pgprot(flags))) { | |
1da177e4 | 117 | vunmap(vma->addr); |
d7c30c68 | 118 | return -EAGAIN; |
1da177e4 | 119 | } |
d7c30c68 PM |
120 | #else |
121 | /* | |
122 | * Without an MMU (or with it turned off), this is much more | |
123 | * straightforward, as we can just load up each queue's QACR with | |
124 | * the physical address appropriately masked. | |
125 | */ | |
126 | ctrl_outl(((map->addr >> 26) << 2) & 0x1c, SQ_QACR0); | |
127 | ctrl_outl(((map->addr >> 26) << 2) & 0x1c, SQ_QACR1); | |
128 | #endif | |
1da177e4 | 129 | |
d7c30c68 | 130 | return 0; |
1da177e4 LT |
131 | } |
132 | ||
133 | /** | |
134 | * sq_remap - Map a physical address through the Store Queues | |
135 | * @phys: Physical address of mapping. | |
136 | * @size: Length of mapping. | |
137 | * @name: User invoking mapping. | |
d7c30c68 | 138 | * @flags: Protection flags. |
1da177e4 LT |
139 | * |
140 | * Remaps the physical address @phys through the next available store queue | |
141 | * address of @size length. @name is logged at boot time as well as through | |
d7c30c68 | 142 | * the sysfs interface. |
1da177e4 | 143 | */ |
d7c30c68 PM |
144 | unsigned long sq_remap(unsigned long phys, unsigned int size, |
145 | const char *name, unsigned long flags) | |
1da177e4 LT |
146 | { |
147 | struct sq_mapping *map; | |
d7c30c68 | 148 | unsigned long end; |
1da177e4 | 149 | unsigned int psz; |
d7c30c68 | 150 | int ret, page; |
1da177e4 LT |
151 | |
152 | /* Don't allow wraparound or zero size */ | |
153 | end = phys + size - 1; | |
d7c30c68 PM |
154 | if (unlikely(!size || end < phys)) |
155 | return -EINVAL; | |
1da177e4 | 156 | /* Don't allow anyone to remap normal memory.. */ |
d7c30c68 PM |
157 | if (unlikely(phys < virt_to_phys(high_memory))) |
158 | return -EINVAL; | |
1da177e4 LT |
159 | |
160 | phys &= PAGE_MASK; | |
d7c30c68 PM |
161 | size = PAGE_ALIGN(end + 1) - phys; |
162 | ||
163 | map = kmem_cache_alloc(sq_cache, GFP_KERNEL); | |
164 | if (unlikely(!map)) | |
165 | return -ENOMEM; | |
166 | ||
167 | map->addr = phys; | |
168 | map->size = size; | |
169 | map->name = name; | |
170 | ||
9f650cf2 | 171 | page = bitmap_find_free_region(sq_bitmap, 0x04000000 >> PAGE_SHIFT, |
d7c30c68 PM |
172 | get_order(map->size)); |
173 | if (unlikely(page < 0)) { | |
174 | ret = -ENOSPC; | |
175 | goto out; | |
176 | } | |
177 | ||
178 | map->sq_addr = P4SEG_STORE_QUE + (page << PAGE_SHIFT); | |
179 | ||
37bda1da | 180 | ret = __sq_remap(map, pgprot_val(PAGE_KERNEL_NOCACHE) | flags); |
d7c30c68 PM |
181 | if (unlikely(ret != 0)) |
182 | goto out; | |
183 | ||
184 | psz = (size + (PAGE_SIZE - 1)) >> PAGE_SHIFT; | |
185 | pr_info("sqremap: %15s [%4d page%s] va 0x%08lx pa 0x%08lx\n", | |
186 | likely(map->name) ? map->name : "???", | |
187 | psz, psz == 1 ? " " : "s", | |
188 | map->sq_addr, map->addr); | |
1da177e4 | 189 | |
d7c30c68 | 190 | sq_mapping_list_add(map); |
1da177e4 | 191 | |
d7c30c68 | 192 | return map->sq_addr; |
1da177e4 | 193 | |
d7c30c68 PM |
194 | out: |
195 | kmem_cache_free(sq_cache, map); | |
196 | return ret; | |
1da177e4 | 197 | } |
9f650cf2 | 198 | EXPORT_SYMBOL(sq_remap); |
1da177e4 LT |
199 | |
200 | /** | |
201 | * sq_unmap - Unmap a Store Queue allocation | |
202 | * @map: Pre-allocated Store Queue mapping. | |
203 | * | |
204 | * Unmaps the store queue allocation @map that was previously created by | |
205 | * sq_remap(). Also frees up the pte that was previously inserted into | |
206 | * the kernel page table and discards the UTLB translation. | |
207 | */ | |
d7c30c68 | 208 | void sq_unmap(unsigned long vaddr) |
1da177e4 | 209 | { |
d7c30c68 | 210 | struct sq_mapping **p, *map; |
d7c30c68 | 211 | int page; |
1da177e4 | 212 | |
d7c30c68 PM |
213 | for (p = &sq_mapping_list; (map = *p); p = &map->next) |
214 | if (map->sq_addr == vaddr) | |
215 | break; | |
1da177e4 | 216 | |
d7c30c68 PM |
217 | if (unlikely(!map)) { |
218 | printk("%s: bad store queue address 0x%08lx\n", | |
219 | __FUNCTION__, vaddr); | |
220 | return; | |
221 | } | |
1da177e4 | 222 | |
d7c30c68 PM |
223 | page = (map->sq_addr - P4SEG_STORE_QUE) >> PAGE_SHIFT; |
224 | bitmap_release_region(sq_bitmap, page, get_order(map->size)); | |
225 | ||
226 | #ifdef CONFIG_MMU | |
b067c50a PM |
227 | { |
228 | /* | |
229 | * Tear down the VMA in the MMU case. | |
230 | */ | |
231 | struct vm_struct *vma; | |
232 | ||
233 | vma = remove_vm_area((void *)(map->sq_addr & PAGE_MASK)); | |
234 | if (!vma) { | |
235 | printk(KERN_ERR "%s: bad address 0x%08lx\n", | |
236 | __FUNCTION__, map->sq_addr); | |
237 | return; | |
238 | } | |
1da177e4 | 239 | } |
d7c30c68 PM |
240 | #endif |
241 | ||
242 | sq_mapping_list_del(map); | |
1da177e4 | 243 | |
d7c30c68 | 244 | kmem_cache_free(sq_cache, map); |
1da177e4 | 245 | } |
9f650cf2 | 246 | EXPORT_SYMBOL(sq_unmap); |
1da177e4 | 247 | |
d7c30c68 PM |
248 | /* |
249 | * Needlessly complex sysfs interface. Unfortunately it doesn't seem like | |
250 | * there is any other easy way to add things on a per-cpu basis without | |
251 | * putting the directory entries somewhere stupid and having to create | |
252 | * links in sysfs by hand back in to the per-cpu directories. | |
1da177e4 | 253 | * |
d7c30c68 PM |
254 | * Some day we may want to have an additional abstraction per store |
255 | * queue, but considering the kobject hell we already have to deal with, | |
256 | * it's simply not worth the trouble. | |
1da177e4 | 257 | */ |
d7c30c68 | 258 | static struct kobject *sq_kobject[NR_CPUS]; |
1da177e4 | 259 | |
d7c30c68 PM |
260 | struct sq_sysfs_attr { |
261 | struct attribute attr; | |
262 | ssize_t (*show)(char *buf); | |
263 | ssize_t (*store)(const char *buf, size_t count); | |
264 | }; | |
1da177e4 | 265 | |
25478445 | 266 | #define to_sq_sysfs_attr(a) container_of(a, struct sq_sysfs_attr, attr) |
1da177e4 | 267 | |
d7c30c68 PM |
268 | static ssize_t sq_sysfs_show(struct kobject *kobj, struct attribute *attr, |
269 | char *buf) | |
270 | { | |
271 | struct sq_sysfs_attr *sattr = to_sq_sysfs_attr(attr); | |
1da177e4 | 272 | |
d7c30c68 PM |
273 | if (likely(sattr->show)) |
274 | return sattr->show(buf); | |
1da177e4 | 275 | |
d7c30c68 | 276 | return -EIO; |
1da177e4 LT |
277 | } |
278 | ||
d7c30c68 PM |
279 | static ssize_t sq_sysfs_store(struct kobject *kobj, struct attribute *attr, |
280 | const char *buf, size_t count) | |
1da177e4 | 281 | { |
d7c30c68 | 282 | struct sq_sysfs_attr *sattr = to_sq_sysfs_attr(attr); |
1da177e4 | 283 | |
d7c30c68 PM |
284 | if (likely(sattr->store)) |
285 | return sattr->store(buf, count); | |
286 | ||
287 | return -EIO; | |
1da177e4 LT |
288 | } |
289 | ||
d7c30c68 PM |
290 | static ssize_t mapping_show(char *buf) |
291 | { | |
292 | struct sq_mapping **list, *entry; | |
293 | char *p = buf; | |
1da177e4 | 294 | |
d7c30c68 PM |
295 | for (list = &sq_mapping_list; (entry = *list); list = &entry->next) |
296 | p += sprintf(p, "%08lx-%08lx [%08lx]: %s\n", | |
297 | entry->sq_addr, entry->sq_addr + entry->size, | |
298 | entry->addr, entry->name); | |
299 | ||
300 | return p - buf; | |
301 | } | |
302 | ||
303 | static ssize_t mapping_store(const char *buf, size_t count) | |
1da177e4 | 304 | { |
d7c30c68 | 305 | unsigned long base = 0, len = 0; |
1da177e4 | 306 | |
d7c30c68 PM |
307 | sscanf(buf, "%lx %lx", &base, &len); |
308 | if (!base) | |
309 | return -EIO; | |
1da177e4 | 310 | |
d7c30c68 PM |
311 | if (likely(len)) { |
312 | int ret = sq_remap(base, len, "Userspace", | |
313 | pgprot_val(PAGE_SHARED)); | |
314 | if (ret < 0) | |
315 | return ret; | |
316 | } else | |
317 | sq_unmap(base); | |
1da177e4 | 318 | |
d7c30c68 PM |
319 | return count; |
320 | } | |
1da177e4 | 321 | |
d7c30c68 PM |
322 | static struct sq_sysfs_attr mapping_attr = |
323 | __ATTR(mapping, 0644, mapping_show, mapping_store); | |
1da177e4 | 324 | |
d7c30c68 PM |
325 | static struct attribute *sq_sysfs_attrs[] = { |
326 | &mapping_attr.attr, | |
327 | NULL, | |
328 | }; | |
1da177e4 | 329 | |
d7c30c68 PM |
330 | static struct sysfs_ops sq_sysfs_ops = { |
331 | .show = sq_sysfs_show, | |
332 | .store = sq_sysfs_store, | |
333 | }; | |
1da177e4 | 334 | |
d7c30c68 PM |
335 | static struct kobj_type ktype_percpu_entry = { |
336 | .sysfs_ops = &sq_sysfs_ops, | |
337 | .default_attrs = sq_sysfs_attrs, | |
338 | }; | |
1da177e4 | 339 | |
d7c30c68 | 340 | static int __devinit sq_sysdev_add(struct sys_device *sysdev) |
1da177e4 | 341 | { |
d7c30c68 PM |
342 | unsigned int cpu = sysdev->id; |
343 | struct kobject *kobj; | |
d48b3352 | 344 | int error; |
1da177e4 | 345 | |
d7c30c68 PM |
346 | sq_kobject[cpu] = kzalloc(sizeof(struct kobject), GFP_KERNEL); |
347 | if (unlikely(!sq_kobject[cpu])) | |
348 | return -ENOMEM; | |
1da177e4 | 349 | |
d7c30c68 | 350 | kobj = sq_kobject[cpu]; |
d48b3352 GKH |
351 | error = kobject_init_and_add(kobj, &ktype_percpu_entry, &sysdev->kobj, |
352 | "%s", "sq"); | |
353 | if (!error) | |
354 | kobject_uevent(kobj, KOBJ_ADD); | |
355 | return error; | |
1da177e4 | 356 | } |
1da177e4 | 357 | |
d7c30c68 PM |
358 | static int __devexit sq_sysdev_remove(struct sys_device *sysdev) |
359 | { | |
360 | unsigned int cpu = sysdev->id; | |
361 | struct kobject *kobj = sq_kobject[cpu]; | |
1da177e4 | 362 | |
38a382ae | 363 | kobject_put(kobj); |
d7c30c68 PM |
364 | return 0; |
365 | } | |
366 | ||
367 | static struct sysdev_driver sq_sysdev_driver = { | |
368 | .add = sq_sysdev_add, | |
369 | .remove = __devexit_p(sq_sysdev_remove), | |
1da177e4 LT |
370 | }; |
371 | ||
372 | static int __init sq_api_init(void) | |
373 | { | |
d7c30c68 PM |
374 | unsigned int nr_pages = 0x04000000 >> PAGE_SHIFT; |
375 | unsigned int size = (nr_pages + (BITS_PER_LONG - 1)) / BITS_PER_LONG; | |
376 | int ret = -ENOMEM; | |
377 | ||
1da177e4 LT |
378 | printk(KERN_NOTICE "sq: Registering store queue API.\n"); |
379 | ||
d7c30c68 | 380 | sq_cache = kmem_cache_create("store_queue_cache", |
20c2df83 | 381 | sizeof(struct sq_mapping), 0, 0, NULL); |
d7c30c68 PM |
382 | if (unlikely(!sq_cache)) |
383 | return ret; | |
1da177e4 | 384 | |
d7c30c68 PM |
385 | sq_bitmap = kzalloc(size, GFP_KERNEL); |
386 | if (unlikely(!sq_bitmap)) | |
387 | goto out; | |
388 | ||
389 | ret = sysdev_driver_register(&cpu_sysdev_class, &sq_sysdev_driver); | |
390 | if (unlikely(ret != 0)) | |
391 | goto out; | |
392 | ||
393 | return 0; | |
394 | ||
395 | out: | |
396 | kfree(sq_bitmap); | |
397 | kmem_cache_destroy(sq_cache); | |
757be186 NH |
398 | |
399 | return ret; | |
1da177e4 LT |
400 | } |
401 | ||
402 | static void __exit sq_api_exit(void) | |
403 | { | |
d7c30c68 PM |
404 | sysdev_driver_unregister(&cpu_sysdev_class, &sq_sysdev_driver); |
405 | kfree(sq_bitmap); | |
406 | kmem_cache_destroy(sq_cache); | |
1da177e4 LT |
407 | } |
408 | ||
409 | module_init(sq_api_init); | |
410 | module_exit(sq_api_exit); | |
411 | ||
412 | MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>, M. R. Brown <mrbrown@0xd6.org>"); | |
413 | MODULE_DESCRIPTION("Simple API for SH-4 integrated Store Queues"); | |
414 | MODULE_LICENSE("GPL"); |