diff options
author | Paul Mundt <lethal@linux-sh.org> | 2006-09-27 02:49:57 -0400 |
---|---|---|
committer | Paul Mundt <lethal@linux-sh.org> | 2006-09-27 02:49:57 -0400 |
commit | d7c30c682a278abe1a52db83f69efec1a9d8f8c2 (patch) | |
tree | 48918e531904edb05944b1e097c386b87e746a24 | |
parent | 373e68b5472d421cbd2703e7a77caf053f78c005 (diff) |
sh: Store Queue API rework.
Rewrite the store queue API for a per-cpu interface in the driver
model. The old miscdevice is dropped, due to TASK_SIZE limitations,
and no one was using it anyways.
Carve up and allocate store queue space with a bitmap, back sq
mapping objects with a slab cache, and let userspace worry about
its own prefetching.
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
-rw-r--r-- | arch/sh/kernel/cpu/sh4/sq.c | 542 | ||||
-rw-r--r-- | include/asm-sh/cpu-sh4/sq.h | 23 |
2 files changed, 251 insertions, 314 deletions
diff --git a/arch/sh/kernel/cpu/sh4/sq.c b/arch/sh/kernel/cpu/sh4/sq.c index b148966dd7c7..7bcc73f9b8df 100644 --- a/arch/sh/kernel/cpu/sh4/sq.c +++ b/arch/sh/kernel/cpu/sh4/sq.c | |||
@@ -1,50 +1,52 @@ | |||
1 | /* | 1 | /* |
2 | * arch/sh/kernel/cpu/sq.c | 2 | * arch/sh/kernel/cpu/sh4/sq.c |
3 | * | 3 | * |
4 | * General management API for SH-4 integrated Store Queues | 4 | * General management API for SH-4 integrated Store Queues |
5 | * | 5 | * |
6 | * Copyright (C) 2001, 2002, 2003, 2004 Paul Mundt | 6 | * Copyright (C) 2001 - 2006 Paul Mundt |
7 | * Copyright (C) 2001, 2002 M. R. Brown | 7 | * Copyright (C) 2001, 2002 M. R. Brown |
8 | * | 8 | * |
9 | * Some of this code has been adopted directly from the old arch/sh/mm/sq.c | ||
10 | * hack that was part of the LinuxDC project. For all intents and purposes, | ||
11 | * this is a completely new interface that really doesn't have much in common | ||
12 | * with the old zone-based approach at all. In fact, it's only listed here for | ||
13 | * general completeness. | ||
14 | * | ||
15 | * This file is subject to the terms and conditions of the GNU General Public | 9 | * This file is subject to the terms and conditions of the GNU General Public |
16 | * License. See the file "COPYING" in the main directory of this archive | 10 | * License. See the file "COPYING" in the main directory of this archive |
17 | * for more details. | 11 | * for more details. |
18 | */ | 12 | */ |
19 | #include <linux/init.h> | 13 | #include <linux/init.h> |
14 | #include <linux/cpu.h> | ||
15 | #include <linux/bitmap.h> | ||
16 | #include <linux/sysdev.h> | ||
20 | #include <linux/kernel.h> | 17 | #include <linux/kernel.h> |
21 | #include <linux/module.h> | 18 | #include <linux/module.h> |
22 | #include <linux/slab.h> | 19 | #include <linux/slab.h> |
23 | #include <linux/list.h> | ||
24 | #include <linux/proc_fs.h> | ||
25 | #include <linux/miscdevice.h> | ||
26 | #include <linux/vmalloc.h> | 20 | #include <linux/vmalloc.h> |
27 | #include <linux/mm.h> | 21 | #include <linux/mm.h> |
28 | #include <asm/io.h> | 22 | #include <asm/io.h> |
29 | #include <asm/page.h> | 23 | #include <asm/page.h> |
30 | #include <asm/cacheflush.h> | 24 | #include <asm/cacheflush.h> |
31 | #include <asm/mmu_context.h> | ||
32 | #include <asm/cpu/sq.h> | 25 | #include <asm/cpu/sq.h> |
33 | 26 | ||
34 | static LIST_HEAD(sq_mapping_list); | 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; | ||
35 | static DEFINE_SPINLOCK(sq_mapping_lock); | 40 | static DEFINE_SPINLOCK(sq_mapping_lock); |
41 | static kmem_cache_t *sq_cache; | ||
42 | static unsigned long *sq_bitmap; | ||
36 | 43 | ||
37 | /** | 44 | #define store_queue_barrier() \ |
38 | * sq_flush - Flush (prefetch) the store queue cache | 45 | do { \ |
39 | * @addr: the store queue address to flush | 46 | (void)ctrl_inl(P4SEG_STORE_QUE); \ |
40 | * | 47 | ctrl_outl(0, P4SEG_STORE_QUE + 0); \ |
41 | * Executes a prefetch instruction on the specified store queue cache, | 48 | ctrl_outl(0, P4SEG_STORE_QUE + 8); \ |
42 | * so that the cached data is written to physical memory. | 49 | } while (0); |
43 | */ | ||
44 | inline void sq_flush(void *addr) | ||
45 | { | ||
46 | __asm__ __volatile__ ("pref @%0" : : "r" (addr) : "memory"); | ||
47 | } | ||
48 | 50 | ||
49 | /** | 51 | /** |
50 | * sq_flush_range - Flush (prefetch) a specific SQ range | 52 | * sq_flush_range - Flush (prefetch) a specific SQ range |
@@ -57,154 +59,73 @@ inline void sq_flush(void *addr) | |||
57 | void sq_flush_range(unsigned long start, unsigned int len) | 59 | void sq_flush_range(unsigned long start, unsigned int len) |
58 | { | 60 | { |
59 | volatile unsigned long *sq = (unsigned long *)start; | 61 | volatile unsigned long *sq = (unsigned long *)start; |
60 | unsigned long dummy; | ||
61 | 62 | ||
62 | /* Flush the queues */ | 63 | /* Flush the queues */ |
63 | for (len >>= 5; len--; sq += 8) | 64 | for (len >>= 5; len--; sq += 8) |
64 | sq_flush((void *)sq); | 65 | prefetchw((void *)sq); |
65 | 66 | ||
66 | /* Wait for completion */ | 67 | /* Wait for completion */ |
67 | dummy = ctrl_inl(P4SEG_STORE_QUE); | 68 | store_queue_barrier(); |
68 | |||
69 | ctrl_outl(0, P4SEG_STORE_QUE + 0); | ||
70 | ctrl_outl(0, P4SEG_STORE_QUE + 8); | ||
71 | } | 69 | } |
72 | 70 | ||
73 | static struct sq_mapping *__sq_alloc_mapping(unsigned long virt, unsigned long phys, unsigned long size, const char *name) | 71 | static inline void sq_mapping_list_add(struct sq_mapping *map) |
74 | { | 72 | { |
75 | struct sq_mapping *map; | 73 | struct sq_mapping **p, *tmp; |
76 | |||
77 | if (virt + size > SQ_ADDRMAX) | ||
78 | return ERR_PTR(-ENOSPC); | ||
79 | 74 | ||
80 | map = kmalloc(sizeof(struct sq_mapping), GFP_KERNEL); | 75 | spin_lock_irq(&sq_mapping_lock); |
81 | if (!map) | ||
82 | return ERR_PTR(-ENOMEM); | ||
83 | 76 | ||
84 | INIT_LIST_HEAD(&map->list); | 77 | p = &sq_mapping_list; |
78 | while ((tmp = *p) != NULL) | ||
79 | p = &tmp->next; | ||
85 | 80 | ||
86 | map->sq_addr = virt; | 81 | map->next = tmp; |
87 | map->addr = phys; | 82 | *p = map; |
88 | map->size = size + 1; | ||
89 | map->name = name; | ||
90 | 83 | ||
91 | list_add(&map->list, &sq_mapping_list); | 84 | spin_unlock_irq(&sq_mapping_lock); |
92 | |||
93 | return map; | ||
94 | } | 85 | } |
95 | 86 | ||
96 | static unsigned long __sq_get_next_addr(void) | 87 | static inline void sq_mapping_list_del(struct sq_mapping *map) |
97 | { | 88 | { |
98 | if (!list_empty(&sq_mapping_list)) { | 89 | struct sq_mapping **p, *tmp; |
99 | struct list_head *pos, *tmp; | 90 | |
100 | 91 | spin_lock_irq(&sq_mapping_lock); | |
101 | /* | 92 | |
102 | * Read one off the list head, as it will have the highest | 93 | for (p = &sq_mapping_list; (tmp = *p); p = &tmp->next) |
103 | * mapped allocation. Set the next one up right above it. | 94 | if (tmp == map) { |
104 | * | 95 | *p = tmp->next; |
105 | * This is somewhat sub-optimal, as we don't look at | 96 | break; |
106 | * gaps between allocations or anything lower then the | ||
107 | * highest-level allocation. | ||
108 | * | ||
109 | * However, in the interest of performance and the general | ||
110 | * lack of desire to do constant list rebalancing, we don't | ||
111 | * worry about it. | ||
112 | */ | ||
113 | list_for_each_safe(pos, tmp, &sq_mapping_list) { | ||
114 | struct sq_mapping *entry; | ||
115 | |||
116 | entry = list_entry(pos, typeof(*entry), list); | ||
117 | |||
118 | return entry->sq_addr + entry->size; | ||
119 | } | 97 | } |
120 | } | ||
121 | 98 | ||
122 | return P4SEG_STORE_QUE; | 99 | spin_unlock_irq(&sq_mapping_lock); |
123 | } | 100 | } |
124 | 101 | ||
125 | /** | 102 | static int __sq_remap(struct sq_mapping *map, unsigned long flags) |
126 | * __sq_remap - Perform a translation from the SQ to a phys addr | ||
127 | * @map: sq mapping containing phys and store queue addresses. | ||
128 | * | ||
129 | * Maps the store queue address specified in the mapping to the physical | ||
130 | * address specified in the mapping. | ||
131 | */ | ||
132 | static struct sq_mapping *__sq_remap(struct sq_mapping *map) | ||
133 | { | 103 | { |
134 | unsigned long flags, pteh, ptel; | 104 | #if defined(CONFIG_MMU) |
135 | struct vm_struct *vma; | 105 | struct vm_struct *vma; |
136 | pgprot_t pgprot; | ||
137 | 106 | ||
138 | /* | ||
139 | * Without an MMU (or with it turned off), this is much more | ||
140 | * straightforward, as we can just load up each queue's QACR with | ||
141 | * the physical address appropriately masked. | ||
142 | */ | ||
143 | |||
144 | ctrl_outl(((map->addr >> 26) << 2) & 0x1c, SQ_QACR0); | ||
145 | ctrl_outl(((map->addr >> 26) << 2) & 0x1c, SQ_QACR1); | ||
146 | |||
147 | #ifdef CONFIG_MMU | ||
148 | /* | ||
149 | * With an MMU on the other hand, things are slightly more involved. | ||
150 | * Namely, we have to have a direct mapping between the SQ addr and | ||
151 | * the associated physical address in the UTLB by way of setting up | ||
152 | * a virt<->phys translation by hand. We do this by simply specifying | ||
153 | * the SQ addr in UTLB.VPN and the associated physical address in | ||
154 | * UTLB.PPN. | ||
155 | * | ||
156 | * Notably, even though this is a special case translation, and some | ||
157 | * of the configuration bits are meaningless, we're still required | ||
158 | * to have a valid ASID context in PTEH. | ||
159 | * | ||
160 | * We could also probably get by without explicitly setting PTEA, but | ||
161 | * we do it here just for good measure. | ||
162 | */ | ||
163 | spin_lock_irqsave(&sq_mapping_lock, flags); | ||
164 | |||
165 | pteh = map->sq_addr; | ||
166 | ctrl_outl((pteh & MMU_VPN_MASK) | get_asid(), MMU_PTEH); | ||
167 | |||
168 | ptel = map->addr & PAGE_MASK; | ||
169 | |||
170 | if (cpu_data->flags & CPU_HAS_PTEA) | ||
171 | ctrl_outl(((ptel >> 28) & 0xe) | (ptel & 0x1), MMU_PTEA); | ||
172 | |||
173 | pgprot = pgprot_noncached(PAGE_KERNEL); | ||
174 | |||
175 | ptel &= _PAGE_FLAGS_HARDWARE_MASK; | ||
176 | ptel |= pgprot_val(pgprot); | ||
177 | ctrl_outl(ptel, MMU_PTEL); | ||
178 | |||
179 | __asm__ __volatile__ ("ldtlb" : : : "memory"); | ||
180 | |||
181 | spin_unlock_irqrestore(&sq_mapping_lock, flags); | ||
182 | |||
183 | /* | ||
184 | * Next, we need to map ourselves in the kernel page table, so that | ||
185 | * future accesses after a TLB flush will be handled when we take a | ||
186 | * page fault. | ||
187 | * | ||
188 | * Theoretically we could just do this directly and not worry about | ||
189 | * setting up the translation by hand ahead of time, but for the | ||
190 | * cases where we want a one-shot SQ mapping followed by a quick | ||
191 | * writeout before we hit the TLB flush, we do it anyways. This way | ||
192 | * we at least save ourselves the initial page fault overhead. | ||
193 | */ | ||
194 | vma = __get_vm_area(map->size, VM_ALLOC, map->sq_addr, SQ_ADDRMAX); | 107 | vma = __get_vm_area(map->size, VM_ALLOC, map->sq_addr, SQ_ADDRMAX); |
195 | if (!vma) | 108 | if (!vma) |
196 | return ERR_PTR(-ENOMEM); | 109 | return -ENOMEM; |
197 | 110 | ||
198 | vma->phys_addr = map->addr; | 111 | vma->phys_addr = map->addr; |
199 | 112 | ||
200 | if (remap_area_pages((unsigned long)vma->addr, vma->phys_addr, | 113 | if (remap_area_pages((unsigned long)vma->addr, vma->phys_addr, |
201 | map->size, pgprot_val(pgprot))) { | 114 | map->size, flags)) { |
202 | vunmap(vma->addr); | 115 | vunmap(vma->addr); |
203 | return NULL; | 116 | return -EAGAIN; |
204 | } | 117 | } |
205 | #endif /* CONFIG_MMU */ | 118 | #else |
119 | /* | ||
120 | * Without an MMU (or with it turned off), this is much more | ||
121 | * straightforward, as we can just load up each queue's QACR with | ||
122 | * the physical address appropriately masked. | ||
123 | */ | ||
124 | ctrl_outl(((map->addr >> 26) << 2) & 0x1c, SQ_QACR0); | ||
125 | ctrl_outl(((map->addr >> 26) << 2) & 0x1c, SQ_QACR1); | ||
126 | #endif | ||
206 | 127 | ||
207 | return map; | 128 | return 0; |
208 | } | 129 | } |
209 | 130 | ||
210 | /** | 131 | /** |
@@ -212,42 +133,65 @@ static struct sq_mapping *__sq_remap(struct sq_mapping *map) | |||
212 | * @phys: Physical address of mapping. | 133 | * @phys: Physical address of mapping. |
213 | * @size: Length of mapping. | 134 | * @size: Length of mapping. |
214 | * @name: User invoking mapping. | 135 | * @name: User invoking mapping. |
136 | * @flags: Protection flags. | ||
215 | * | 137 | * |
216 | * Remaps the physical address @phys through the next available store queue | 138 | * Remaps the physical address @phys through the next available store queue |
217 | * address of @size length. @name is logged at boot time as well as through | 139 | * address of @size length. @name is logged at boot time as well as through |
218 | * the procfs interface. | 140 | * the sysfs interface. |
219 | * | ||
220 | * A pre-allocated and filled sq_mapping pointer is returned, and must be | ||
221 | * cleaned up with a call to sq_unmap() when the user is done with the | ||
222 | * mapping. | ||
223 | */ | 141 | */ |
224 | struct sq_mapping *sq_remap(unsigned long phys, unsigned int size, const char *name) | 142 | unsigned long sq_remap(unsigned long phys, unsigned int size, |
143 | const char *name, unsigned long flags) | ||
225 | { | 144 | { |
226 | struct sq_mapping *map; | 145 | struct sq_mapping *map; |
227 | unsigned long virt, end; | 146 | unsigned long end; |
228 | unsigned int psz; | 147 | unsigned int psz; |
148 | int ret, page; | ||
229 | 149 | ||
230 | /* Don't allow wraparound or zero size */ | 150 | /* Don't allow wraparound or zero size */ |
231 | end = phys + size - 1; | 151 | end = phys + size - 1; |
232 | if (!size || end < phys) | 152 | if (unlikely(!size || end < phys)) |
233 | return NULL; | 153 | return -EINVAL; |
234 | /* Don't allow anyone to remap normal memory.. */ | 154 | /* Don't allow anyone to remap normal memory.. */ |
235 | if (phys < virt_to_phys(high_memory)) | 155 | if (unlikely(phys < virt_to_phys(high_memory))) |
236 | return NULL; | 156 | return -EINVAL; |
237 | 157 | ||
238 | phys &= PAGE_MASK; | 158 | phys &= PAGE_MASK; |
159 | size = PAGE_ALIGN(end + 1) - phys; | ||
160 | |||
161 | map = kmem_cache_alloc(sq_cache, GFP_KERNEL); | ||
162 | if (unlikely(!map)) | ||
163 | return -ENOMEM; | ||
164 | |||
165 | map->addr = phys; | ||
166 | map->size = size; | ||
167 | map->name = name; | ||
168 | |||
169 | page = bitmap_find_free_region(sq_bitmap, 0x04000000, | ||
170 | get_order(map->size)); | ||
171 | if (unlikely(page < 0)) { | ||
172 | ret = -ENOSPC; | ||
173 | goto out; | ||
174 | } | ||
175 | |||
176 | map->sq_addr = P4SEG_STORE_QUE + (page << PAGE_SHIFT); | ||
177 | |||
178 | ret = __sq_remap(map, flags); | ||
179 | if (unlikely(ret != 0)) | ||
180 | goto out; | ||
181 | |||
182 | psz = (size + (PAGE_SIZE - 1)) >> PAGE_SHIFT; | ||
183 | pr_info("sqremap: %15s [%4d page%s] va 0x%08lx pa 0x%08lx\n", | ||
184 | likely(map->name) ? map->name : "???", | ||
185 | psz, psz == 1 ? " " : "s", | ||
186 | map->sq_addr, map->addr); | ||
239 | 187 | ||
240 | size = PAGE_ALIGN(end + 1) - phys; | 188 | sq_mapping_list_add(map); |
241 | virt = __sq_get_next_addr(); | ||
242 | psz = (size + (PAGE_SIZE - 1)) / PAGE_SIZE; | ||
243 | map = __sq_alloc_mapping(virt, phys, size, name); | ||
244 | 189 | ||
245 | printk("sqremap: %15s [%4d page%s] va 0x%08lx pa 0x%08lx\n", | 190 | return map->sq_addr; |
246 | map->name ? map->name : "???", | ||
247 | psz, psz == 1 ? " " : "s", | ||
248 | map->sq_addr, map->addr); | ||
249 | 191 | ||
250 | return __sq_remap(map); | 192 | out: |
193 | kmem_cache_free(sq_cache, map); | ||
194 | return ret; | ||
251 | } | 195 | } |
252 | 196 | ||
253 | /** | 197 | /** |
@@ -258,188 +202,198 @@ struct sq_mapping *sq_remap(unsigned long phys, unsigned int size, const char *n | |||
258 | * sq_remap(). Also frees up the pte that was previously inserted into | 202 | * sq_remap(). Also frees up the pte that was previously inserted into |
259 | * the kernel page table and discards the UTLB translation. | 203 | * the kernel page table and discards the UTLB translation. |
260 | */ | 204 | */ |
261 | void sq_unmap(struct sq_mapping *map) | 205 | void sq_unmap(unsigned long vaddr) |
262 | { | 206 | { |
263 | if (map->sq_addr > (unsigned long)high_memory) | 207 | struct sq_mapping **p, *map; |
264 | vfree((void *)(map->sq_addr & PAGE_MASK)); | 208 | struct vm_struct *vma; |
209 | int page; | ||
265 | 210 | ||
266 | list_del(&map->list); | 211 | for (p = &sq_mapping_list; (map = *p); p = &map->next) |
267 | kfree(map); | 212 | if (map->sq_addr == vaddr) |
268 | } | 213 | break; |
269 | 214 | ||
270 | /** | 215 | if (unlikely(!map)) { |
271 | * sq_clear - Clear a store queue range | 216 | printk("%s: bad store queue address 0x%08lx\n", |
272 | * @addr: Address to start clearing from. | 217 | __FUNCTION__, vaddr); |
273 | * @len: Length to clear. | 218 | return; |
274 | * | 219 | } |
275 | * A quick zero-fill implementation for clearing out memory that has been | ||
276 | * remapped through the store queues. | ||
277 | */ | ||
278 | void sq_clear(unsigned long addr, unsigned int len) | ||
279 | { | ||
280 | int i; | ||
281 | 220 | ||
282 | /* Clear out both queues linearly */ | 221 | page = (map->sq_addr - P4SEG_STORE_QUE) >> PAGE_SHIFT; |
283 | for (i = 0; i < 8; i++) { | 222 | bitmap_release_region(sq_bitmap, page, get_order(map->size)); |
284 | ctrl_outl(0, addr + i + 0); | 223 | |
285 | ctrl_outl(0, addr + i + 8); | 224 | #ifdef CONFIG_MMU |
225 | vma = remove_vm_area((void *)(map->sq_addr & PAGE_MASK)); | ||
226 | if (!vma) { | ||
227 | printk(KERN_ERR "%s: bad address 0x%08lx\n", | ||
228 | __FUNCTION__, map->sq_addr); | ||
229 | return; | ||
286 | } | 230 | } |
231 | #endif | ||
232 | |||
233 | sq_mapping_list_del(map); | ||
287 | 234 | ||
288 | sq_flush_range(addr, len); | 235 | kmem_cache_free(sq_cache, map); |
289 | } | 236 | } |
290 | 237 | ||
291 | /** | 238 | /* |
292 | * sq_vma_unmap - Unmap a VMA range | 239 | * Needlessly complex sysfs interface. Unfortunately it doesn't seem like |
293 | * @area: VMA containing range. | 240 | * there is any other easy way to add things on a per-cpu basis without |
294 | * @addr: Start of range. | 241 | * putting the directory entries somewhere stupid and having to create |
295 | * @len: Length of range. | 242 | * links in sysfs by hand back in to the per-cpu directories. |
296 | * | 243 | * |
297 | * Searches the sq_mapping_list for a mapping matching the sq addr @addr, | 244 | * Some day we may want to have an additional abstraction per store |
298 | * and subsequently frees up the entry. Further cleanup is done by generic | 245 | * queue, but considering the kobject hell we already have to deal with, |
299 | * code. | 246 | * it's simply not worth the trouble. |
300 | */ | 247 | */ |
301 | static void sq_vma_unmap(struct vm_area_struct *area, | 248 | static struct kobject *sq_kobject[NR_CPUS]; |
302 | unsigned long addr, size_t len) | ||
303 | { | ||
304 | struct list_head *pos, *tmp; | ||
305 | 249 | ||
306 | list_for_each_safe(pos, tmp, &sq_mapping_list) { | 250 | struct sq_sysfs_attr { |
307 | struct sq_mapping *entry; | 251 | struct attribute attr; |
252 | ssize_t (*show)(char *buf); | ||
253 | ssize_t (*store)(const char *buf, size_t count); | ||
254 | }; | ||
308 | 255 | ||
309 | entry = list_entry(pos, typeof(*entry), list); | 256 | #define to_sq_sysfs_attr(attr) container_of(attr, struct sq_sysfs_attr, attr) |
310 | 257 | ||
311 | if (entry->sq_addr == addr) { | 258 | static ssize_t sq_sysfs_show(struct kobject *kobj, struct attribute *attr, |
312 | /* | 259 | char *buf) |
313 | * We could probably get away without doing the tlb flush | 260 | { |
314 | * here, as generic code should take care of most of this | 261 | struct sq_sysfs_attr *sattr = to_sq_sysfs_attr(attr); |
315 | * when unmapping the rest of the VMA range for us. Leave | ||
316 | * it in for added sanity for the time being.. | ||
317 | */ | ||
318 | __flush_tlb_page(get_asid(), entry->sq_addr & PAGE_MASK); | ||
319 | 262 | ||
320 | list_del(&entry->list); | 263 | if (likely(sattr->show)) |
321 | kfree(entry); | 264 | return sattr->show(buf); |
322 | 265 | ||
323 | return; | 266 | return -EIO; |
324 | } | ||
325 | } | ||
326 | } | 267 | } |
327 | 268 | ||
328 | /** | 269 | static ssize_t sq_sysfs_store(struct kobject *kobj, struct attribute *attr, |
329 | * sq_vma_sync - Sync a VMA range | 270 | const char *buf, size_t count) |
330 | * @area: VMA containing range. | ||
331 | * @start: Start of range. | ||
332 | * @len: Length of range. | ||
333 | * @flags: Additional flags. | ||
334 | * | ||
335 | * Synchronizes an sq mapped range by flushing the store queue cache for | ||
336 | * the duration of the mapping. | ||
337 | * | ||
338 | * Used internally for user mappings, which must use msync() to prefetch | ||
339 | * the store queue cache. | ||
340 | */ | ||
341 | static int sq_vma_sync(struct vm_area_struct *area, | ||
342 | unsigned long start, size_t len, unsigned int flags) | ||
343 | { | 271 | { |
344 | sq_flush_range(start, len); | 272 | struct sq_sysfs_attr *sattr = to_sq_sysfs_attr(attr); |
345 | 273 | ||
346 | return 0; | 274 | if (likely(sattr->store)) |
275 | return sattr->store(buf, count); | ||
276 | |||
277 | return -EIO; | ||
347 | } | 278 | } |
348 | 279 | ||
349 | static struct vm_operations_struct sq_vma_ops = { | 280 | static ssize_t mapping_show(char *buf) |
350 | .unmap = sq_vma_unmap, | 281 | { |
351 | .sync = sq_vma_sync, | 282 | struct sq_mapping **list, *entry; |
352 | }; | 283 | char *p = buf; |
353 | 284 | ||
354 | /** | 285 | for (list = &sq_mapping_list; (entry = *list); list = &entry->next) |
355 | * sq_mmap - mmap() for /dev/cpu/sq | 286 | p += sprintf(p, "%08lx-%08lx [%08lx]: %s\n", |
356 | * @file: unused. | 287 | entry->sq_addr, entry->sq_addr + entry->size, |
357 | * @vma: VMA to remap. | 288 | entry->addr, entry->name); |
358 | * | 289 | |
359 | * Remap the specified vma @vma through the store queues, and setup associated | 290 | return p - buf; |
360 | * information for the new mapping. Also build up the page tables for the new | 291 | } |
361 | * area. | 292 | |
362 | */ | 293 | static ssize_t mapping_store(const char *buf, size_t count) |
363 | static int sq_mmap(struct file *file, struct vm_area_struct *vma) | ||
364 | { | 294 | { |
365 | unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; | 295 | unsigned long base = 0, len = 0; |
366 | unsigned long size = vma->vm_end - vma->vm_start; | ||
367 | struct sq_mapping *map; | ||
368 | 296 | ||
369 | /* | 297 | sscanf(buf, "%lx %lx", &base, &len); |
370 | * We're not interested in any arbitrary virtual address that has | 298 | if (!base) |
371 | * been stuck in the VMA, as we already know what addresses we | 299 | return -EIO; |
372 | * want. Save off the size, and reposition the VMA to begin at | ||
373 | * the next available sq address. | ||
374 | */ | ||
375 | vma->vm_start = __sq_get_next_addr(); | ||
376 | vma->vm_end = vma->vm_start + size; | ||
377 | 300 | ||
378 | vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); | 301 | if (likely(len)) { |
302 | int ret = sq_remap(base, len, "Userspace", | ||
303 | pgprot_val(PAGE_SHARED)); | ||
304 | if (ret < 0) | ||
305 | return ret; | ||
306 | } else | ||
307 | sq_unmap(base); | ||
379 | 308 | ||
380 | vma->vm_flags |= VM_IO | VM_RESERVED; | 309 | return count; |
310 | } | ||
381 | 311 | ||
382 | map = __sq_alloc_mapping(vma->vm_start, offset, size, "Userspace"); | 312 | static struct sq_sysfs_attr mapping_attr = |
313 | __ATTR(mapping, 0644, mapping_show, mapping_store); | ||
383 | 314 | ||
384 | if (io_remap_pfn_range(vma, map->sq_addr, map->addr >> PAGE_SHIFT, | 315 | static struct attribute *sq_sysfs_attrs[] = { |
385 | size, vma->vm_page_prot)) | 316 | &mapping_attr.attr, |
386 | return -EAGAIN; | 317 | NULL, |
318 | }; | ||
387 | 319 | ||
388 | vma->vm_ops = &sq_vma_ops; | 320 | static struct sysfs_ops sq_sysfs_ops = { |
321 | .show = sq_sysfs_show, | ||
322 | .store = sq_sysfs_store, | ||
323 | }; | ||
389 | 324 | ||
390 | return 0; | 325 | static struct kobj_type ktype_percpu_entry = { |
391 | } | 326 | .sysfs_ops = &sq_sysfs_ops, |
327 | .default_attrs = sq_sysfs_attrs, | ||
328 | }; | ||
392 | 329 | ||
393 | #ifdef CONFIG_PROC_FS | 330 | static int __devinit sq_sysdev_add(struct sys_device *sysdev) |
394 | static int sq_mapping_read_proc(char *buf, char **start, off_t off, | ||
395 | int len, int *eof, void *data) | ||
396 | { | 331 | { |
397 | struct list_head *pos; | 332 | unsigned int cpu = sysdev->id; |
398 | char *p = buf; | 333 | struct kobject *kobj; |
399 | 334 | ||
400 | list_for_each_prev(pos, &sq_mapping_list) { | 335 | sq_kobject[cpu] = kzalloc(sizeof(struct kobject), GFP_KERNEL); |
401 | struct sq_mapping *entry; | 336 | if (unlikely(!sq_kobject[cpu])) |
337 | return -ENOMEM; | ||
402 | 338 | ||
403 | entry = list_entry(pos, typeof(*entry), list); | 339 | kobj = sq_kobject[cpu]; |
340 | kobj->parent = &sysdev->kobj; | ||
341 | kobject_set_name(kobj, "%s", "sq"); | ||
342 | kobj->ktype = &ktype_percpu_entry; | ||
404 | 343 | ||
405 | p += sprintf(p, "%08lx-%08lx [%08lx]: %s\n", entry->sq_addr, | 344 | return kobject_register(kobj); |
406 | entry->sq_addr + entry->size - 1, entry->addr, | ||
407 | entry->name); | ||
408 | } | ||
409 | |||
410 | return p - buf; | ||
411 | } | 345 | } |
412 | #endif | ||
413 | 346 | ||
414 | static struct file_operations sq_fops = { | 347 | static int __devexit sq_sysdev_remove(struct sys_device *sysdev) |
415 | .owner = THIS_MODULE, | 348 | { |
416 | .mmap = sq_mmap, | 349 | unsigned int cpu = sysdev->id; |
417 | }; | 350 | struct kobject *kobj = sq_kobject[cpu]; |
418 | 351 | ||
419 | static struct miscdevice sq_dev = { | 352 | kobject_unregister(kobj); |
420 | .minor = STORE_QUEUE_MINOR, | 353 | return 0; |
421 | .name = "sq", | 354 | } |
422 | .fops = &sq_fops, | 355 | |
356 | static struct sysdev_driver sq_sysdev_driver = { | ||
357 | .add = sq_sysdev_add, | ||
358 | .remove = __devexit_p(sq_sysdev_remove), | ||
423 | }; | 359 | }; |
424 | 360 | ||
425 | static int __init sq_api_init(void) | 361 | static int __init sq_api_init(void) |
426 | { | 362 | { |
427 | int ret; | 363 | unsigned int nr_pages = 0x04000000 >> PAGE_SHIFT; |
364 | unsigned int size = (nr_pages + (BITS_PER_LONG - 1)) / BITS_PER_LONG; | ||
365 | int ret = -ENOMEM; | ||
366 | |||
428 | printk(KERN_NOTICE "sq: Registering store queue API.\n"); | 367 | printk(KERN_NOTICE "sq: Registering store queue API.\n"); |
429 | 368 | ||
430 | create_proc_read_entry("sq_mapping", 0, 0, sq_mapping_read_proc, 0); | 369 | sq_cache = kmem_cache_create("store_queue_cache", |
370 | sizeof(struct sq_mapping), 0, 0, | ||
371 | NULL, NULL); | ||
372 | if (unlikely(!sq_cache)) | ||
373 | return ret; | ||
431 | 374 | ||
432 | ret = misc_register(&sq_dev); | 375 | sq_bitmap = kzalloc(size, GFP_KERNEL); |
433 | if (ret) | 376 | if (unlikely(!sq_bitmap)) |
434 | remove_proc_entry("sq_mapping", NULL); | 377 | goto out; |
378 | |||
379 | ret = sysdev_driver_register(&cpu_sysdev_class, &sq_sysdev_driver); | ||
380 | if (unlikely(ret != 0)) | ||
381 | goto out; | ||
382 | |||
383 | return 0; | ||
384 | |||
385 | out: | ||
386 | kfree(sq_bitmap); | ||
387 | kmem_cache_destroy(sq_cache); | ||
435 | 388 | ||
436 | return ret; | 389 | return ret; |
437 | } | 390 | } |
438 | 391 | ||
439 | static void __exit sq_api_exit(void) | 392 | static void __exit sq_api_exit(void) |
440 | { | 393 | { |
441 | misc_deregister(&sq_dev); | 394 | sysdev_driver_unregister(&cpu_sysdev_class, &sq_sysdev_driver); |
442 | remove_proc_entry("sq_mapping", NULL); | 395 | kfree(sq_bitmap); |
396 | kmem_cache_destroy(sq_cache); | ||
443 | } | 397 | } |
444 | 398 | ||
445 | module_init(sq_api_init); | 399 | module_init(sq_api_init); |
@@ -448,11 +402,7 @@ module_exit(sq_api_exit); | |||
448 | MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>, M. R. Brown <mrbrown@0xd6.org>"); | 402 | MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>, M. R. Brown <mrbrown@0xd6.org>"); |
449 | MODULE_DESCRIPTION("Simple API for SH-4 integrated Store Queues"); | 403 | MODULE_DESCRIPTION("Simple API for SH-4 integrated Store Queues"); |
450 | MODULE_LICENSE("GPL"); | 404 | MODULE_LICENSE("GPL"); |
451 | MODULE_ALIAS_MISCDEV(STORE_QUEUE_MINOR); | ||
452 | 405 | ||
453 | EXPORT_SYMBOL(sq_remap); | 406 | EXPORT_SYMBOL(sq_remap); |
454 | EXPORT_SYMBOL(sq_unmap); | 407 | EXPORT_SYMBOL(sq_unmap); |
455 | EXPORT_SYMBOL(sq_clear); | ||
456 | EXPORT_SYMBOL(sq_flush); | ||
457 | EXPORT_SYMBOL(sq_flush_range); | 408 | EXPORT_SYMBOL(sq_flush_range); |
458 | |||
diff --git a/include/asm-sh/cpu-sh4/sq.h b/include/asm-sh/cpu-sh4/sq.h index 366b09166d3b..586d6491816a 100644 --- a/include/asm-sh/cpu-sh4/sq.h +++ b/include/asm-sh/cpu-sh4/sq.h | |||
@@ -17,7 +17,7 @@ | |||
17 | * Store queues range from e0000000-e3fffffc, allowing approx. 64MB to be | 17 | * Store queues range from e0000000-e3fffffc, allowing approx. 64MB to be |
18 | * mapped to any physical address space. Since data is written (and aligned) | 18 | * mapped to any physical address space. Since data is written (and aligned) |
19 | * to 32-byte boundaries, we need to be sure that all allocations are aligned. | 19 | * to 32-byte boundaries, we need to be sure that all allocations are aligned. |
20 | */ | 20 | */ |
21 | #define SQ_SIZE 32 | 21 | #define SQ_SIZE 32 |
22 | #define SQ_ALIGN_MASK (~(SQ_SIZE - 1)) | 22 | #define SQ_ALIGN_MASK (~(SQ_SIZE - 1)) |
23 | #define SQ_ALIGN(addr) (((addr)+SQ_SIZE-1) & SQ_ALIGN_MASK) | 23 | #define SQ_ALIGN(addr) (((addr)+SQ_SIZE-1) & SQ_ALIGN_MASK) |
@@ -26,23 +26,10 @@ | |||
26 | #define SQ_QACR1 (P4SEG_REG_BASE + 0x3c) | 26 | #define SQ_QACR1 (P4SEG_REG_BASE + 0x3c) |
27 | #define SQ_ADDRMAX (P4SEG_STORE_QUE + 0x04000000) | 27 | #define SQ_ADDRMAX (P4SEG_STORE_QUE + 0x04000000) |
28 | 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 list_head list; | ||
37 | }; | ||
38 | |||
39 | /* arch/sh/kernel/cpu/sh4/sq.c */ | 29 | /* arch/sh/kernel/cpu/sh4/sq.c */ |
40 | extern struct sq_mapping *sq_remap(unsigned long phys, unsigned int size, const char *name); | 30 | unsigned long sq_remap(unsigned long phys, unsigned int size, |
41 | extern void sq_unmap(struct sq_mapping *map); | 31 | const char *name, unsigned long flags); |
42 | 32 | void sq_unmap(unsigned long vaddr); | |
43 | extern void sq_clear(unsigned long addr, unsigned int len); | 33 | void sq_flush_range(unsigned long start, unsigned int len); |
44 | extern void sq_flush(void *addr); | ||
45 | extern void sq_flush_range(unsigned long start, unsigned int len); | ||
46 | 34 | ||
47 | #endif /* __ASM_CPU_SH4_SQ_H */ | 35 | #endif /* __ASM_CPU_SH4_SQ_H */ |
48 | |||