aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGlenn Elliott <gelliott@cs.unc.edu>2012-03-04 21:20:45 -0500
committerGlenn Elliott <gelliott@cs.unc.edu>2012-03-04 21:20:45 -0500
commit3e41d4826b0aa175c3f194548fa6ab20cd1cc32d (patch)
tree73220ecd0947dd5b991344fe994180c571a473f2
parent12d312072e3f4caa6e4e500d5a23c85402494cd1 (diff)
Clean up PAI.
-rw-r--r--litmus/sched_cedf.c179
-rw-r--r--litmus/sched_gsn_edf.c170
2 files changed, 15 insertions, 334 deletions
diff --git a/litmus/sched_cedf.c b/litmus/sched_cedf.c
index 3251fb1602f8..394caf9329b4 100644
--- a/litmus/sched_cedf.c
+++ b/litmus/sched_cedf.c
@@ -466,79 +466,8 @@ static void __do_lit_tasklet(struct tasklet_struct* tasklet, unsigned long flush
466} 466}
467 467
468 468
469static void __extract_tasklets(cedf_domain_t* cluster, struct task_struct* task, struct tasklet_head* task_tasklets)
470{
471 struct tasklet_struct* step;
472 struct tasklet_struct* tasklet;
473 struct tasklet_struct* prev;
474
475 task_tasklets->head = NULL;
476 task_tasklets->tail = &(task_tasklets->head);
477
478 prev = NULL;
479 for(step = cluster->pending_tasklets.head; step != NULL; step = step->next)
480 {
481 if(step->owner == task)
482 {
483 TRACE("%s: Found tasklet to flush: %d\n", __FUNCTION__, step->owner->pid);
484
485 tasklet = step;
486
487 if(prev) {
488 prev->next = tasklet->next;
489 }
490 else if(cluster->pending_tasklets.head == tasklet) {
491 // we're at the head.
492 cluster->pending_tasklets.head = tasklet->next;
493 }
494
495 if(cluster->pending_tasklets.tail == &tasklet) {
496 // we're at the tail
497 if(prev) {
498 cluster->pending_tasklets.tail = &prev;
499 }
500 else {
501 cluster->pending_tasklets.tail = &(cluster->pending_tasklets.head);
502 }
503 }
504
505 tasklet->next = NULL;
506 *(task_tasklets->tail) = tasklet;
507 task_tasklets->tail = &(tasklet->next);
508 }
509 else {
510 prev = step;
511 }
512 }
513}
514
515static void flush_tasklets(cedf_domain_t* cluster, struct task_struct* task) 469static void flush_tasklets(cedf_domain_t* cluster, struct task_struct* task)
516{ 470{
517#if 0
518 unsigned long flags;
519 struct tasklet_head task_tasklets;
520 struct tasklet_struct* step;
521
522 raw_spin_lock_irqsave(&cluster->cluster_lock, flags);
523 __extract_tasklets(cluster, task, &task_tasklets);
524 raw_spin_unlock_irqrestore(&cluster->cluster_lock, flags);
525
526 if(cluster->pending_tasklets.head != NULL) {
527 TRACE("%s: Flushing tasklets for %d...\n", __FUNCTION__, task->pid);
528 }
529
530 // now execute any flushed tasklets.
531 for(step = cluster->pending_tasklets.head; step != NULL; /**/)
532 {
533 struct tasklet_struct* temp = step->next;
534
535 step->next = NULL;
536 __do_lit_tasklet(step, 1ul);
537
538 step = temp;
539 }
540#endif
541
542 // lazy flushing. 471 // lazy flushing.
543 // just change ownership to NULL and let an idle processor 472 // just change ownership to NULL and let an idle processor
544 // take care of it. :P 473 // take care of it. :P
@@ -548,10 +477,8 @@ static void flush_tasklets(cedf_domain_t* cluster, struct task_struct* task)
548 477
549 raw_spin_lock_irqsave(&cluster->cluster_lock, flags); 478 raw_spin_lock_irqsave(&cluster->cluster_lock, flags);
550 479
551 for(step = cluster->pending_tasklets.head; step != NULL; step = step->next) 480 for(step = cluster->pending_tasklets.head; step != NULL; step = step->next) {
552 { 481 if(step->owner == task) {
553 if(step->owner == task)
554 {
555 TRACE("%s: Found tasklet to flush: %d\n", __FUNCTION__, step->owner->pid); 482 TRACE("%s: Found tasklet to flush: %d\n", __FUNCTION__, step->owner->pid);
556 step->owner = NULL; 483 step->owner = NULL;
557 } 484 }
@@ -565,7 +492,6 @@ static void do_lit_tasklets(cedf_domain_t* cluster, struct task_struct* sched_ta
565{ 492{
566 int work_to_do = 1; 493 int work_to_do = 1;
567 struct tasklet_struct *tasklet = NULL; 494 struct tasklet_struct *tasklet = NULL;
568 //struct tasklet_struct *step;
569 unsigned long flags; 495 unsigned long flags;
570 496
571 while(work_to_do) { 497 while(work_to_do) {
@@ -575,17 +501,6 @@ static void do_lit_tasklets(cedf_domain_t* cluster, struct task_struct* sched_ta
575 // remove tasklet at head of list if it has higher priority. 501 // remove tasklet at head of list if it has higher priority.
576 raw_spin_lock_irqsave(&cluster->cluster_lock, flags); 502 raw_spin_lock_irqsave(&cluster->cluster_lock, flags);
577 503
578/*
579 step = cluster->pending_tasklets.head;
580 TRACE("%s: (BEFORE) dumping tasklet queue...\n", __FUNCTION__);
581 while(step != NULL){
582 TRACE("%s: %p (%d)\n", __FUNCTION__, step, step->owner->pid);
583 step = step->next;
584 }
585 TRACE("%s: tail = %p (%d)\n", __FUNCTION__, *(cluster->pending_tasklets.tail), (*(cluster->pending_tasklets.tail) != NULL) ? (*(cluster->pending_tasklets.tail))->owner->pid : -1);
586 TRACE("%s: done.\n", __FUNCTION__);
587 */
588
589 if(cluster->pending_tasklets.head != NULL) { 504 if(cluster->pending_tasklets.head != NULL) {
590 // remove tasklet at head. 505 // remove tasklet at head.
591 tasklet = cluster->pending_tasklets.head; 506 tasklet = cluster->pending_tasklets.head;
@@ -612,21 +527,8 @@ static void do_lit_tasklets(cedf_domain_t* cluster, struct task_struct* sched_ta
612 TRACE("%s: Tasklet queue is empty.\n", __FUNCTION__); 527 TRACE("%s: Tasklet queue is empty.\n", __FUNCTION__);
613 } 528 }
614 529
615
616 /*
617 step = cluster->pending_tasklets.head;
618 TRACE("%s: (AFTER) dumping tasklet queue...\n", __FUNCTION__);
619 while(step != NULL){
620 TRACE("%s: %p (%d)\n", __FUNCTION__, step, step->owner->pid);
621 step = step->next;
622 }
623 TRACE("%s: tail = %p (%d)\n", __FUNCTION__, *(cluster->pending_tasklets.tail), (*(cluster->pending_tasklets.tail) != NULL) ? (*(cluster->pending_tasklets.tail))->owner->pid : -1);
624 TRACE("%s: done.\n", __FUNCTION__);
625 */
626
627 raw_spin_unlock_irqrestore(&cluster->cluster_lock, flags); 530 raw_spin_unlock_irqrestore(&cluster->cluster_lock, flags);
628 531
629
630 TS_NV_SCHED_BOTISR_END; 532 TS_NV_SCHED_BOTISR_END;
631 533
632 if(tasklet) { 534 if(tasklet) {
@@ -637,8 +539,6 @@ static void do_lit_tasklets(cedf_domain_t* cluster, struct task_struct* sched_ta
637 work_to_do = 0; 539 work_to_do = 0;
638 } 540 }
639 } 541 }
640
641 //TRACE("%s: exited.\n", __FUNCTION__);
642} 542}
643 543
644 544
@@ -646,28 +546,6 @@ static void run_tasklets(struct task_struct* sched_task)
646{ 546{
647 cedf_domain_t* cluster; 547 cedf_domain_t* cluster;
648 548
649#if 0
650 int task_is_rt = is_realtime(sched_task);
651 cedf_domain_t* cluster;
652
653 if(is_realtime(sched_task)) {
654 cluster = task_cpu_cluster(sched_task);
655 }
656 else {
657 cluster = remote_cluster(get_cpu());
658 }
659
660 if(cluster && cluster->pending_tasklets.head != NULL) {
661 TRACE("%s: There are tasklets to process.\n", __FUNCTION__);
662
663 do_lit_tasklets(cluster, sched_task);
664 }
665
666 if(!task_is_rt) {
667 put_cpu_no_resched();
668 }
669#else
670
671 preempt_disable(); 549 preempt_disable();
672 550
673 cluster = (is_realtime(sched_task)) ? 551 cluster = (is_realtime(sched_task)) ?
@@ -680,8 +558,6 @@ static void run_tasklets(struct task_struct* sched_task)
680 } 558 }
681 559
682 preempt_enable_no_resched(); 560 preempt_enable_no_resched();
683
684#endif
685} 561}
686 562
687 563
@@ -689,18 +565,6 @@ static void __add_pai_tasklet(struct tasklet_struct* tasklet, cedf_domain_t* clu
689{ 565{
690 struct tasklet_struct* step; 566 struct tasklet_struct* step;
691 567
692 /*
693 step = cluster->pending_tasklets.head;
694 TRACE("%s: (BEFORE) dumping tasklet queue...\n", __FUNCTION__);
695 while(step != NULL){
696 TRACE("%s: %p (%d)\n", __FUNCTION__, step, step->owner->pid);
697 step = step->next;
698 }
699 TRACE("%s: tail = %p (%d)\n", __FUNCTION__, *(cluster->pending_tasklets.tail), (*(cluster->pending_tasklets.tail) != NULL) ? (*(cluster->pending_tasklets.tail))->owner->pid : -1);
700 TRACE("%s: done.\n", __FUNCTION__);
701 */
702
703
704 tasklet->next = NULL; // make sure there are no old values floating around 568 tasklet->next = NULL; // make sure there are no old values floating around
705 569
706 step = cluster->pending_tasklets.head; 570 step = cluster->pending_tasklets.head;
@@ -720,8 +584,6 @@ static void __add_pai_tasklet(struct tasklet_struct* tasklet, cedf_domain_t* clu
720 } 584 }
721 else { 585 else {
722 586
723 //WARN_ON(1 == 1);
724
725 // insert the tasklet somewhere in the middle. 587 // insert the tasklet somewhere in the middle.
726 588
727 TRACE("%s: tasklet belongs somewhere in the middle.\n", __FUNCTION__); 589 TRACE("%s: tasklet belongs somewhere in the middle.\n", __FUNCTION__);
@@ -753,22 +615,6 @@ static void __add_pai_tasklet(struct tasklet_struct* tasklet, cedf_domain_t* clu
753 cluster->pending_tasklets.head = tasklet; 615 cluster->pending_tasklets.head = tasklet;
754 } 616 }
755 } 617 }
756
757 /*
758 step = cluster->pending_tasklets.head;
759 TRACE("%s: (AFTER) dumping tasklet queue...\n", __FUNCTION__);
760 while(step != NULL){
761 TRACE("%s: %p (%d)\n", __FUNCTION__, step, step->owner->pid);
762 step = step->next;
763 }
764 TRACE("%s: tail = %p (%d)\n", __FUNCTION__, *(cluster->pending_tasklets.tail), (*(cluster->pending_tasklets.tail) != NULL) ? (*(cluster->pending_tasklets.tail))->owner->pid : -1);
765 TRACE("%s: done.\n", __FUNCTION__);
766 */
767
768// TODO: Maintain this list in priority order.
769// tasklet->next = NULL;
770// *(cluster->pending_tasklets.tail) = tasklet;
771// cluster->pending_tasklets.tail = &tasklet->next;
772} 618}
773 619
774static int enqueue_pai_tasklet(struct tasklet_struct* tasklet) 620static int enqueue_pai_tasklet(struct tasklet_struct* tasklet)
@@ -792,7 +638,6 @@ static int enqueue_pai_tasklet(struct tasklet_struct* tasklet)
792 638
793 thisCPU = smp_processor_id(); 639 thisCPU = smp_processor_id();
794 640
795#if 1
796#ifdef CONFIG_SCHED_CPU_AFFINITY 641#ifdef CONFIG_SCHED_CPU_AFFINITY
797 { 642 {
798 cpu_entry_t* affinity = NULL; 643 cpu_entry_t* affinity = NULL;
@@ -813,7 +658,6 @@ static int enqueue_pai_tasklet(struct tasklet_struct* tasklet)
813 targetCPU = affinity; 658 targetCPU = affinity;
814 } 659 }
815#endif 660#endif
816#endif
817 661
818 if (targetCPU == NULL) { 662 if (targetCPU == NULL) {
819 targetCPU = lowest_prio_cpu(cluster); 663 targetCPU = lowest_prio_cpu(cluster);
@@ -859,24 +703,7 @@ static int enqueue_pai_tasklet(struct tasklet_struct* tasklet)
859 return(1); // success 703 return(1); // success
860} 704}
861 705
862 706#endif // PAI
863#endif
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880 707
881/* Getting schedule() right is a bit tricky. schedule() may not make any 708/* Getting schedule() right is a bit tricky. schedule() may not make any
882 * assumptions on the state of the current task since it may be called for a 709 * assumptions on the state of the current task since it may be called for a
diff --git a/litmus/sched_gsn_edf.c b/litmus/sched_gsn_edf.c
index 3ddab5875c8c..0d86587915c5 100644
--- a/litmus/sched_gsn_edf.c
+++ b/litmus/sched_gsn_edf.c
@@ -448,76 +448,23 @@ static void __do_lit_tasklet(struct tasklet_struct* tasklet, unsigned long flush
448} 448}
449 449
450 450
451static void __extract_tasklets(struct task_struct* task, struct tasklet_head* task_tasklets) 451static void flush_tasklets(struct task_struct* task)
452{ 452{
453 // lazy flushing.
454 // just change ownership to NULL and let idel processor
455 // take care of it. :P
456
453 struct tasklet_struct* step; 457 struct tasklet_struct* step;
454 struct tasklet_struct* tasklet; 458 unsigned long flags;
455 struct tasklet_struct* prev; 459
456 460 raw_spin_lock_irqsave(&gsnedf_lock, flags);
457 task_tasklets->head = NULL; 461 for(step = gsnedf_pending_tasklets.head; step != NULL; step = step->next) {
458 task_tasklets->tail = &(task_tasklets->head); 462 if(step->owner == task) {
459
460 prev = NULL;
461 for(step = gsnedf_pending_tasklets.head; step != NULL; step = step->next)
462 {
463 if(step->owner == task)
464 {
465 TRACE("%s: Found tasklet to flush: %d\n", __FUNCTION__, step->owner->pid); 463 TRACE("%s: Found tasklet to flush: %d\n", __FUNCTION__, step->owner->pid);
466 464 step->owner = NULL;
467 tasklet = step;
468
469 if(prev) {
470 prev->next = tasklet->next;
471 }
472 else if(gsnedf_pending_tasklets.head == tasklet) {
473 // we're at the head.
474 gsnedf_pending_tasklets.head = tasklet->next;
475 }
476
477 if(gsnedf_pending_tasklets.tail == &tasklet) {
478 // we're at the tail
479 if(prev) {
480 gsnedf_pending_tasklets.tail = &prev;
481 }
482 else {
483 gsnedf_pending_tasklets.tail = &(gsnedf_pending_tasklets.head);
484 }
485 }
486
487 tasklet->next = NULL;
488 *(task_tasklets->tail) = tasklet;
489 task_tasklets->tail = &(tasklet->next);
490 }
491 else {
492 prev = step;
493 } 465 }
494 } 466 }
495}
496
497static void flush_tasklets(struct task_struct* task)
498{
499 unsigned long flags;
500 struct tasklet_head task_tasklets;
501 struct tasklet_struct* step;
502
503 raw_spin_lock_irqsave(&gsnedf_lock, flags);
504 __extract_tasklets(task, &task_tasklets);
505 raw_spin_unlock_irqrestore(&gsnedf_lock, flags); 467 raw_spin_unlock_irqrestore(&gsnedf_lock, flags);
506
507 if(gsnedf_pending_tasklets.head != NULL) {
508 TRACE("%s: Flushing tasklets for %d...\n", __FUNCTION__, task->pid);
509 }
510
511 // now execute any flushed tasklets.
512 for(step = gsnedf_pending_tasklets.head; step != NULL; /**/)
513 {
514 struct tasklet_struct* temp = step->next;
515
516 step->next = NULL;
517 __do_lit_tasklet(step, 1ul);
518
519 step = temp;
520 }
521} 468}
522 469
523 470
@@ -535,18 +482,6 @@ static void do_lit_tasklets(struct task_struct* sched_task)
535 // remove tasklet at head of list if it has higher priority. 482 // remove tasklet at head of list if it has higher priority.
536 raw_spin_lock_irqsave(&gsnedf_lock, flags); 483 raw_spin_lock_irqsave(&gsnedf_lock, flags);
537 484
538 /*
539 step = gsnedf_pending_tasklets.head;
540 TRACE("%s: (BEFORE) dumping tasklet queue...\n", __FUNCTION__);
541 while(step != NULL){
542 TRACE("%s: %p (%d)\n", __FUNCTION__, step, step->owner->pid);
543 step = step->next;
544 }
545 TRACE("%s: tail = %p (%d)\n", __FUNCTION__, *(gsnedf_pending_tasklets.tail), (*(gsnedf_pending_tasklets.tail) != NULL) ? (*(gsnedf_pending_tasklets.tail))->owner->pid : -1);
546 TRACE("%s: done.\n", __FUNCTION__);
547 */
548
549
550 if(gsnedf_pending_tasklets.head != NULL) { 485 if(gsnedf_pending_tasklets.head != NULL) {
551 // remove tasklet at head. 486 // remove tasklet at head.
552 tasklet = gsnedf_pending_tasklets.head; 487 tasklet = gsnedf_pending_tasklets.head;
@@ -573,18 +508,6 @@ static void do_lit_tasklets(struct task_struct* sched_task)
573 TRACE("%s: Tasklet queue is empty.\n", __FUNCTION__); 508 TRACE("%s: Tasklet queue is empty.\n", __FUNCTION__);
574 } 509 }
575 510
576
577 /*
578 step = gsnedf_pending_tasklets.head;
579 TRACE("%s: (AFTER) dumping tasklet queue...\n", __FUNCTION__);
580 while(step != NULL){
581 TRACE("%s: %p (%d)\n", __FUNCTION__, step, step->owner->pid);
582 step = step->next;
583 }
584 TRACE("%s: tail = %p (%d)\n", __FUNCTION__, *(gsnedf_pending_tasklets.tail), (*(gsnedf_pending_tasklets.tail) != NULL) ? (*(gsnedf_pending_tasklets.tail))->owner->pid : -1);
585 TRACE("%s: done.\n", __FUNCTION__);
586 */
587
588 raw_spin_unlock_irqrestore(&gsnedf_lock, flags); 511 raw_spin_unlock_irqrestore(&gsnedf_lock, flags);
589 512
590 TS_NV_SCHED_BOTISR_END; 513 TS_NV_SCHED_BOTISR_END;
@@ -604,28 +527,6 @@ static void do_lit_tasklets(struct task_struct* sched_task)
604 527
605static void run_tasklets(struct task_struct* sched_task) 528static void run_tasklets(struct task_struct* sched_task)
606{ 529{
607#if 0
608 int task_is_rt = is_realtime(sched_task);
609 cedf_domain_t* cluster;
610
611 if(is_realtime(sched_task)) {
612 cluster = task_cpu_cluster(sched_task);
613 }
614 else {
615 cluster = remote_cluster(get_cpu());
616 }
617
618 if(cluster && gsnedf_pending_tasklets.head != NULL) {
619 TRACE("%s: There are tasklets to process.\n", __FUNCTION__);
620
621 do_lit_tasklets(cluster, sched_task);
622 }
623
624 if(!task_is_rt) {
625 put_cpu_no_resched();
626 }
627#else
628
629 preempt_disable(); 530 preempt_disable();
630 531
631 if(gsnedf_pending_tasklets.head != NULL) { 532 if(gsnedf_pending_tasklets.head != NULL) {
@@ -634,8 +535,6 @@ static void run_tasklets(struct task_struct* sched_task)
634 } 535 }
635 536
636 preempt_enable_no_resched(); 537 preempt_enable_no_resched();
637
638#endif
639} 538}
640 539
641 540
@@ -643,18 +542,6 @@ static void __add_pai_tasklet(struct tasklet_struct* tasklet)
643{ 542{
644 struct tasklet_struct* step; 543 struct tasklet_struct* step;
645 544
646 /*
647 step = gsnedf_pending_tasklets.head;
648 TRACE("%s: (BEFORE) dumping tasklet queue...\n", __FUNCTION__);
649 while(step != NULL){
650 TRACE("%s: %p (%d)\n", __FUNCTION__, step, step->owner->pid);
651 step = step->next;
652 }
653 TRACE("%s: tail = %p (%d)\n", __FUNCTION__, *(gsnedf_pending_tasklets.tail), (*(gsnedf_pending_tasklets.tail) != NULL) ? (*(gsnedf_pending_tasklets.tail))->owner->pid : -1);
654 TRACE("%s: done.\n", __FUNCTION__);
655 */
656
657
658 tasklet->next = NULL; // make sure there are no old values floating around 545 tasklet->next = NULL; // make sure there are no old values floating around
659 546
660 step = gsnedf_pending_tasklets.head; 547 step = gsnedf_pending_tasklets.head;
@@ -673,9 +560,6 @@ static void __add_pai_tasklet(struct tasklet_struct* tasklet)
673 gsnedf_pending_tasklets.tail = &(tasklet->next); 560 gsnedf_pending_tasklets.tail = &(tasklet->next);
674 } 561 }
675 else { 562 else {
676
677 //WARN_ON(1 == 1);
678
679 // insert the tasklet somewhere in the middle. 563 // insert the tasklet somewhere in the middle.
680 564
681 TRACE("%s: tasklet belongs somewhere in the middle.\n", __FUNCTION__); 565 TRACE("%s: tasklet belongs somewhere in the middle.\n", __FUNCTION__);
@@ -698,22 +582,6 @@ static void __add_pai_tasklet(struct tasklet_struct* tasklet)
698 gsnedf_pending_tasklets.head = tasklet; 582 gsnedf_pending_tasklets.head = tasklet;
699 } 583 }
700 } 584 }
701
702 /*
703 step = gsnedf_pending_tasklets.head;
704 TRACE("%s: (AFTER) dumping tasklet queue...\n", __FUNCTION__);
705 while(step != NULL){
706 TRACE("%s: %p (%d)\n", __FUNCTION__, step, step->owner->pid);
707 step = step->next;
708 }
709 TRACE("%s: tail = %p (%d)\n", __FUNCTION__, *(gsnedf_pending_tasklets.tail), (*(gsnedf_pending_tasklets.tail) != NULL) ? (*(gsnedf_pending_tasklets.tail))->owner->pid : -1);
710 TRACE("%s: done.\n", __FUNCTION__);
711 */
712
713 // TODO: Maintain this list in priority order.
714 // tasklet->next = NULL;
715 // *(gsnedf_pending_tasklets.tail) = tasklet;
716 // gsnedf_pending_tasklets.tail = &tasklet->next;
717} 585}
718 586
719static int enqueue_pai_tasklet(struct tasklet_struct* tasklet) 587static int enqueue_pai_tasklet(struct tasklet_struct* tasklet)
@@ -735,7 +603,6 @@ static int enqueue_pai_tasklet(struct tasklet_struct* tasklet)
735 603
736 thisCPU = smp_processor_id(); 604 thisCPU = smp_processor_id();
737 605
738#if 1
739#ifdef CONFIG_SCHED_CPU_AFFINITY 606#ifdef CONFIG_SCHED_CPU_AFFINITY
740 { 607 {
741 cpu_entry_t* affinity = NULL; 608 cpu_entry_t* affinity = NULL;
@@ -760,7 +627,6 @@ static int enqueue_pai_tasklet(struct tasklet_struct* tasklet)
760 targetCPU = affinity; 627 targetCPU = affinity;
761 } 628 }
762#endif 629#endif
763#endif
764 630
765 if (targetCPU == NULL) { 631 if (targetCPU == NULL) {
766 targetCPU = lowest_prio_cpu(); 632 targetCPU = lowest_prio_cpu();
@@ -806,19 +672,7 @@ static int enqueue_pai_tasklet(struct tasklet_struct* tasklet)
806 return(1); // success 672 return(1); // success
807} 673}
808 674
809 675#endif // end PAI
810#endif
811
812
813
814
815
816
817
818
819
820
821
822 676
823 677
824/* Getting schedule() right is a bit tricky. schedule() may not make any 678/* Getting schedule() right is a bit tricky. schedule() may not make any