aboutsummaryrefslogtreecommitdiffstats
path: root/arch/powerpc/platforms/cell/spufs/sched.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/powerpc/platforms/cell/spufs/sched.c')
-rw-r--r--arch/powerpc/platforms/cell/spufs/sched.c419
1 files changed, 419 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/cell/spufs/sched.c b/arch/powerpc/platforms/cell/spufs/sched.c
new file mode 100644
index 00000000000..c0d9d83a9ac
--- /dev/null
+++ b/arch/powerpc/platforms/cell/spufs/sched.c
@@ -0,0 +1,419 @@
1/* sched.c - SPU scheduler.
2 *
3 * Copyright (C) IBM 2005
4 * Author: Mark Nutter <mnutter@us.ibm.com>
5 *
6 * SPU scheduler, based on Linux thread priority. For now use
7 * a simple "cooperative" yield model with no preemption. SPU
8 * scheduling will eventually be preemptive: When a thread with
9 * a higher static priority gets ready to run, then an active SPU
10 * context will be preempted and returned to the waitq.
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2, or (at your option)
15 * any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 */
26
27#define DEBUG 1
28#include <linux/config.h>
29#include <linux/module.h>
30#include <linux/errno.h>
31#include <linux/sched.h>
32#include <linux/kernel.h>
33#include <linux/mm.h>
34#include <linux/completion.h>
35#include <linux/vmalloc.h>
36#include <linux/smp.h>
37#include <linux/smp_lock.h>
38#include <linux/stddef.h>
39#include <linux/unistd.h>
40
41#include <asm/io.h>
42#include <asm/mmu_context.h>
43#include <asm/spu.h>
44#include <asm/spu_csa.h>
45#include "spufs.h"
46
47#define SPU_BITMAP_SIZE (((MAX_PRIO+BITS_PER_LONG)/BITS_PER_LONG)+1)
48struct spu_prio_array {
49 atomic_t nr_blocked;
50 unsigned long bitmap[SPU_BITMAP_SIZE];
51 wait_queue_head_t waitq[MAX_PRIO];
52};
53
54/* spu_runqueue - This is the main runqueue data structure for SPUs. */
55struct spu_runqueue {
56 struct semaphore sem;
57 unsigned long nr_active;
58 unsigned long nr_idle;
59 unsigned long nr_switches;
60 struct list_head active_list;
61 struct list_head idle_list;
62 struct spu_prio_array prio;
63};
64
65static struct spu_runqueue *spu_runqueues = NULL;
66
67static inline struct spu_runqueue *spu_rq(void)
68{
69 /* Future: make this a per-NODE array,
70 * and use cpu_to_node(smp_processor_id())
71 */
72 return spu_runqueues;
73}
74
75static inline struct spu *del_idle(struct spu_runqueue *rq)
76{
77 struct spu *spu;
78
79 BUG_ON(rq->nr_idle <= 0);
80 BUG_ON(list_empty(&rq->idle_list));
81 /* Future: Move SPU out of low-power SRI state. */
82 spu = list_entry(rq->idle_list.next, struct spu, sched_list);
83 list_del_init(&spu->sched_list);
84 rq->nr_idle--;
85 return spu;
86}
87
88static inline void del_active(struct spu_runqueue *rq, struct spu *spu)
89{
90 BUG_ON(rq->nr_active <= 0);
91 BUG_ON(list_empty(&rq->active_list));
92 list_del_init(&spu->sched_list);
93 rq->nr_active--;
94}
95
96static inline void add_idle(struct spu_runqueue *rq, struct spu *spu)
97{
98 /* Future: Put SPU into low-power SRI state. */
99 list_add_tail(&spu->sched_list, &rq->idle_list);
100 rq->nr_idle++;
101}
102
103static inline void add_active(struct spu_runqueue *rq, struct spu *spu)
104{
105 rq->nr_active++;
106 rq->nr_switches++;
107 list_add_tail(&spu->sched_list, &rq->active_list);
108}
109
110static void prio_wakeup(struct spu_runqueue *rq)
111{
112 if (atomic_read(&rq->prio.nr_blocked) && rq->nr_idle) {
113 int best = sched_find_first_bit(rq->prio.bitmap);
114 if (best < MAX_PRIO) {
115 wait_queue_head_t *wq = &rq->prio.waitq[best];
116 wake_up_interruptible_nr(wq, 1);
117 }
118 }
119}
120
121static void prio_wait(struct spu_runqueue *rq, u64 flags)
122{
123 int prio = current->prio;
124 wait_queue_head_t *wq = &rq->prio.waitq[prio];
125 DEFINE_WAIT(wait);
126
127 __set_bit(prio, rq->prio.bitmap);
128 atomic_inc(&rq->prio.nr_blocked);
129 prepare_to_wait_exclusive(wq, &wait, TASK_INTERRUPTIBLE);
130 if (!signal_pending(current)) {
131 up(&rq->sem);
132 pr_debug("%s: pid=%d prio=%d\n", __FUNCTION__,
133 current->pid, current->prio);
134 schedule();
135 down(&rq->sem);
136 }
137 finish_wait(wq, &wait);
138 atomic_dec(&rq->prio.nr_blocked);
139 if (!waitqueue_active(wq))
140 __clear_bit(prio, rq->prio.bitmap);
141}
142
143static inline int is_best_prio(struct spu_runqueue *rq)
144{
145 int best_prio;
146
147 best_prio = sched_find_first_bit(rq->prio.bitmap);
148 return (current->prio < best_prio) ? 1 : 0;
149}
150
151static inline void mm_needs_global_tlbie(struct mm_struct *mm)
152{
153 /* Global TLBIE broadcast required with SPEs. */
154#if (NR_CPUS > 1)
155 __cpus_setall(&mm->cpu_vm_mask, NR_CPUS);
156#else
157 __cpus_setall(&mm->cpu_vm_mask, NR_CPUS+1); /* is this ok? */
158#endif
159}
160
161static inline void bind_context(struct spu *spu, struct spu_context *ctx)
162{
163 pr_debug("%s: pid=%d SPU=%d\n", __FUNCTION__, current->pid,
164 spu->number);
165 spu->ctx = ctx;
166 spu->flags = 0;
167 ctx->spu = spu;
168 ctx->ops = &spu_hw_ops;
169 spu->pid = current->pid;
170 spu->prio = current->prio;
171 spu->mm = ctx->owner;
172 mm_needs_global_tlbie(spu->mm);
173 spu->ibox_callback = spufs_ibox_callback;
174 spu->wbox_callback = spufs_wbox_callback;
175 mb();
176 spu_restore(&ctx->csa, spu);
177}
178
179static inline void unbind_context(struct spu *spu, struct spu_context *ctx)
180{
181 pr_debug("%s: unbind pid=%d SPU=%d\n", __FUNCTION__,
182 spu->pid, spu->number);
183 spu_save(&ctx->csa, spu);
184 ctx->state = SPU_STATE_SAVED;
185 spu->ibox_callback = NULL;
186 spu->wbox_callback = NULL;
187 spu->mm = NULL;
188 spu->pid = 0;
189 spu->prio = MAX_PRIO;
190 ctx->ops = &spu_backing_ops;
191 ctx->spu = NULL;
192 spu->ctx = NULL;
193}
194
195static struct spu *preempt_active(struct spu_runqueue *rq)
196{
197 struct list_head *p;
198 struct spu_context *ctx;
199 struct spu *spu;
200
201 /* Future: implement real preemption. For now just
202 * boot a lower priority ctx that is in "detached"
203 * state, i.e. on a processor but not currently in
204 * spu_run().
205 */
206 list_for_each(p, &rq->active_list) {
207 spu = list_entry(p, struct spu, sched_list);
208 if (current->prio < spu->prio) {
209 ctx = spu->ctx;
210 if (down_write_trylock(&ctx->state_sema)) {
211 if (ctx->state != SPU_STATE_RUNNABLE) {
212 up_write(&ctx->state_sema);
213 continue;
214 }
215 pr_debug("%s: booting pid=%d from SPU %d\n",
216 __FUNCTION__, spu->pid, spu->number);
217 del_active(rq, spu);
218 up(&rq->sem);
219 unbind_context(spu, ctx);
220 up_write(&ctx->state_sema);
221 return spu;
222 }
223 }
224 }
225 return NULL;
226}
227
228static struct spu *get_idle_spu(u64 flags)
229{
230 struct spu_runqueue *rq;
231 struct spu *spu = NULL;
232
233 rq = spu_rq();
234 down(&rq->sem);
235 for (;;) {
236 if (rq->nr_idle > 0) {
237 if (is_best_prio(rq)) {
238 /* Fall through. */
239 spu = del_idle(rq);
240 break;
241 } else {
242 prio_wakeup(rq);
243 up(&rq->sem);
244 yield();
245 if (signal_pending(current)) {
246 return NULL;
247 }
248 rq = spu_rq();
249 down(&rq->sem);
250 continue;
251 }
252 } else {
253 if (is_best_prio(rq)) {
254 if ((spu = preempt_active(rq)) != NULL)
255 return spu;
256 }
257 prio_wait(rq, flags);
258 if (signal_pending(current)) {
259 prio_wakeup(rq);
260 spu = NULL;
261 break;
262 }
263 continue;
264 }
265 }
266 up(&rq->sem);
267 return spu;
268}
269
270static void put_idle_spu(struct spu *spu)
271{
272 struct spu_runqueue *rq = spu->rq;
273
274 down(&rq->sem);
275 add_idle(rq, spu);
276 prio_wakeup(rq);
277 up(&rq->sem);
278}
279
280static int get_active_spu(struct spu *spu)
281{
282 struct spu_runqueue *rq = spu->rq;
283 struct list_head *p;
284 struct spu *tmp;
285 int rc = 0;
286
287 down(&rq->sem);
288 list_for_each(p, &rq->active_list) {
289 tmp = list_entry(p, struct spu, sched_list);
290 if (tmp == spu) {
291 del_active(rq, spu);
292 rc = 1;
293 break;
294 }
295 }
296 up(&rq->sem);
297 return rc;
298}
299
300static void put_active_spu(struct spu *spu)
301{
302 struct spu_runqueue *rq = spu->rq;
303
304 down(&rq->sem);
305 add_active(rq, spu);
306 up(&rq->sem);
307}
308
309/* Lock order:
310 * spu_activate() & spu_deactivate() require the
311 * caller to have down_write(&ctx->state_sema).
312 *
313 * The rq->sem is breifly held (inside or outside a
314 * given ctx lock) for list management, but is never
315 * held during save/restore.
316 */
317
318int spu_activate(struct spu_context *ctx, u64 flags)
319{
320 struct spu *spu;
321
322 if (ctx->spu)
323 return 0;
324 spu = get_idle_spu(flags);
325 if (!spu)
326 return (signal_pending(current)) ? -ERESTARTSYS : -EAGAIN;
327 bind_context(spu, ctx);
328 put_active_spu(spu);
329 return 0;
330}
331
332void spu_deactivate(struct spu_context *ctx)
333{
334 struct spu *spu;
335 int needs_idle;
336
337 spu = ctx->spu;
338 if (!spu)
339 return;
340 needs_idle = get_active_spu(spu);
341 unbind_context(spu, ctx);
342 if (needs_idle)
343 put_idle_spu(spu);
344}
345
346void spu_yield(struct spu_context *ctx)
347{
348 struct spu *spu;
349
350 if (!down_write_trylock(&ctx->state_sema))
351 return;
352 spu = ctx->spu;
353 if ((ctx->state == SPU_STATE_RUNNABLE) &&
354 (sched_find_first_bit(spu->rq->prio.bitmap) <= current->prio)) {
355 pr_debug("%s: yielding SPU %d\n", __FUNCTION__, spu->number);
356 spu_deactivate(ctx);
357 ctx->state = SPU_STATE_SAVED;
358 }
359 up_write(&ctx->state_sema);
360}
361
362int __init spu_sched_init(void)
363{
364 struct spu_runqueue *rq;
365 struct spu *spu;
366 int i;
367
368 rq = spu_runqueues = kmalloc(sizeof(struct spu_runqueue), GFP_KERNEL);
369 if (!rq) {
370 printk(KERN_WARNING "%s: Unable to allocate runqueues.\n",
371 __FUNCTION__);
372 return 1;
373 }
374 memset(rq, 0, sizeof(struct spu_runqueue));
375 init_MUTEX(&rq->sem);
376 INIT_LIST_HEAD(&rq->active_list);
377 INIT_LIST_HEAD(&rq->idle_list);
378 rq->nr_active = 0;
379 rq->nr_idle = 0;
380 rq->nr_switches = 0;
381 atomic_set(&rq->prio.nr_blocked, 0);
382 for (i = 0; i < MAX_PRIO; i++) {
383 init_waitqueue_head(&rq->prio.waitq[i]);
384 __clear_bit(i, rq->prio.bitmap);
385 }
386 __set_bit(MAX_PRIO, rq->prio.bitmap);
387 for (;;) {
388 spu = spu_alloc();
389 if (!spu)
390 break;
391 pr_debug("%s: adding SPU[%d]\n", __FUNCTION__, spu->number);
392 add_idle(rq, spu);
393 spu->rq = rq;
394 }
395 if (!rq->nr_idle) {
396 printk(KERN_WARNING "%s: No available SPUs.\n", __FUNCTION__);
397 kfree(rq);
398 return 1;
399 }
400 return 0;
401}
402
403void __exit spu_sched_exit(void)
404{
405 struct spu_runqueue *rq = spu_rq();
406 struct spu *spu;
407
408 if (!rq) {
409 printk(KERN_WARNING "%s: no runqueues!\n", __FUNCTION__);
410 return;
411 }
412 while (rq->nr_idle > 0) {
413 spu = del_idle(rq);
414 if (!spu)
415 break;
416 spu_free(spu);
417 }
418 kfree(rq);
419}