/* * litmus/rt_domain.c * * LITMUS real-time infrastructure. This file contains the * functions that manipulate RT domains. RT domains are an abstraction * of a ready queue and a release queue. */ #include #include #include #include #include #include #include #include #include #include /* Uncomment when debugging timer races... */ #if 0 #define VTRACE_TASK TRACE_TASK #define VTRACE TRACE #else #define VTRACE_TASK(t, fmt, args...) /* shut up */ #define VTRACE(fmt, args...) /* be quiet already */ #endif static int dummy_resched(rt_domain_t *rt) { return 0; } static int dummy_order(struct bheap_node* a, struct bheap_node* b) { return 0; } /* default implementation: use default lock */ static void default_release_jobs(rt_domain_t* rt, struct bheap* tasks) { merge_ready(rt, tasks); } static unsigned int time2slot(lt_t time) { return (unsigned int) time2quanta(time, FLOOR) % RELEASE_QUEUE_SLOTS; } static enum hrtimer_restart on_release_timer(struct hrtimer *timer) { unsigned long flags; struct release_heap* rh; rh = container_of(timer, struct release_heap, timer); TS_RELEASE_LATENCY(rh->release_time); VTRACE("on_release_timer(0x%p) starts.\n", timer); TS_RELEASE_START; raw_spin_lock_irqsave(&rh->dom->release_lock, flags); VTRACE("CB has the release_lock 0x%p\n", &rh->dom->release_lock); /* remove from release queue */ list_del(&rh->list); raw_spin_unlock_irqrestore(&rh->dom->release_lock, flags); VTRACE("CB returned release_lock 0x%p\n", &rh->dom->release_lock); /* call release callback */ rh->dom->release_jobs(rh->dom, &rh->heap); /* WARNING: rh can be referenced from other CPUs from now on. */ TS_RELEASE_END; VTRACE("on_release_timer(0x%p) ends.\n", timer); return HRTIMER_NORESTART; } /* allocated in litmus.c */ struct kmem_cache * release_heap_cache; struct release_heap* release_heap_alloc(int gfp_flags) { struct release_heap* rh; rh= kmem_cache_alloc(release_heap_cache, gfp_flags); if (rh) { /* initialize timer */ hrtimer_init(&rh->timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); rh->timer.function = on_release_timer; } return rh; } void release_heap_free(struct release_heap* rh) { /* make sure timer is no longer in use */ hrtimer_cancel(&rh->timer); kmem_cache_free(release_heap_cache, rh); } /* Caller must hold release lock. * Will return heap for given time. If no such heap exists prior to * the invocation it will be created. */ static struct release_heap* get_release_heap(rt_domain_t *rt, struct task_struct* t, int use_task_heap) { struct list_head* pos; struct release_heap* heap = NULL; struct release_heap* rh; lt_t release_time = get_release(t); unsigned int slot = time2slot(release_time); /* initialize pos for the case that the list is empty */ pos = rt->release_queue.slot[slot].next; list_for_each(pos, &rt->release_queue.slot[slot]) { rh = list_entry(pos, struct release_heap, list); if (release_time == rh->release_time) { /* perfect match -- this happens on hyperperiod * boundaries */ heap = rh; break; } else if (lt_before(release_time, rh->release_time)) { /* we need to insert a new node since rh is * already in the future */ break; } } if (!heap && use_task_heap) { /* use pre-allocated release heap */ rh = tsk_rt(t)->rel_heap; rh->dom = rt; rh->release_time = release_time; /* add to release queue */ list_add(&rh->list, pos->prev); heap = rh; } return heap; } static void reinit_release_heap(struct task_struct* t) { struct release_heap* rh; /* use pre-allocated release heap */ rh = tsk_rt(t)->rel_heap; /* Make sure it is safe to use. The timer callback could still * be executing on another CPU; hrtimer_cancel() will wait * until the timer callback has completed. However, under no * circumstances should the timer be active (= yet to be * triggered). * * WARNING: If the CPU still holds the release_lock at this point, * deadlock may occur! */ BUG_ON(hrtimer_cancel(&rh->timer)); /* initialize */ bheap_init(&rh->heap); #ifdef CONFIG_RELEASE_MASTER atomic_set(&rh->info.state, HRTIMER_START_ON_INACTIVE); #endif } /* arm_release_timer() - start local release timer or trigger * remote timer (pull timer) * * Called by add_release() with: * - tobe_lock taken * - IRQ disabled */ #ifdef CONFIG_RELEASE_MASTER #define arm_release_timer(t) arm_release_timer_on((t), NO_CPU) static void arm_release_timer_on(rt_domain_t *_rt , int target_cpu) #else static void arm_release_timer(rt_domain_t *_rt) #endif { rt_domain_t *rt = _rt; struct list_head list; struct list_head *pos, *safe; struct task_struct* t; struct release_heap* rh; VTRACE("arm_release_timer() at %llu\n", litmus_clock()); list_replace_init(&rt->tobe_released, &list); list_for_each_safe(pos, safe, &list) { /* pick task of work list */ t = list_entry(pos, struct task_struct, rt_param.list); sched_trace_task_release(t); list_del(pos); /* put into release heap while holding release_lock */ raw_spin_lock(&rt->release_lock); VTRACE_TASK(t, "I have the release_lock 0x%p\n", &rt->release_lock); rh = get_release_heap(rt, t, 0); if (!rh) { /* need to use our own, but drop lock first */ raw_spin_unlock(&rt->release_lock); VTRACE_TASK(t, "Dropped release_lock 0x%p\n", &rt->release_lock); reinit_release_heap(t); VTRACE_TASK(t, "release_heap ready\n"); raw_spin_lock(&rt->release_lock); VTRACE_TASK(t, "Re-acquired release_lock 0x%p\n", &rt->release_lock); rh = get_release_heap(rt, t, 1); } bheap_insert(rt->order, &rh->heap, tsk_rt(t)->heap_node); VTRACE_TASK(t, "arm_release_timer(): added to release heap\n"); raw_spin_unlock(&rt->release_lock); VTRACE_TASK(t, "Returned the release_lock 0x%p\n", &rt->release_lock); /* To avoid arming the timer multiple times, we only let the * owner do the arming (which is the "first" task to reference * this release_heap anyway). */ if (rh == tsk_rt(t)->rel_heap) { VTRACE_TASK(t, "arming timer 0x%p\n", &rh->timer); /* we cannot arm the timer using hrtimer_start() * as it may deadlock on rq->lock * * PINNED mode is ok on both local and remote CPU */ #ifdef CONFIG_RELEASE_MASTER if (rt->release_master == NO_CPU && target_cpu == NO_CPU) #endif __hrtimer_start_range_ns(&rh->timer, ns_to_ktime(rh->release_time), 0, HRTIMER_MODE_ABS_PINNED, 0); #ifdef CONFIG_RELEASE_MASTER else hrtimer_start_on( /* target_cpu overrides release master */ (target_cpu != NO_CPU ? target_cpu : rt->release_master), &rh->info, &rh->timer, ns_to_ktime(rh->release_time), HRTIMER_MODE_ABS_PINNED); #endif } else VTRACE_TASK(t, "0x%p is not my timer\n", &rh->timer); } } void rt_domain_init(rt_domain_t *rt, bheap_prio_t order, check_resched_needed_t check, release_jobs_t release ) { int i; BUG_ON(!rt); if (!check) check = dummy_resched; if (!release) release = default_release_jobs; if (!order) order = dummy_order; #ifdef CONFIG_RELEASE_MASTER rt->release_master = NO_CPU; #endif bheap_init(&rt->ready_queue); INIT_LIST_HEAD(&rt->tobe_released); for (i = 0; i < RELEASE_QUEUE_SLOTS; i++) INIT_LIST_HEAD(&rt->release_queue.slot[i]); raw_spin_lock_init(&rt->ready_lock); raw_spin_lock_init(&rt->release_lock); raw_spin_lock_init(&rt->tobe_lock); rt->check_resched = check; rt->release_jobs = release; rt->order = order; } /* add_ready - add a real-time task to the rt ready queue. It must be runnable. * @new: the newly released task */ void __add_ready(rt_domain_t* rt, struct task_struct *new) { TRACE("rt: adding %s/%d (%llu, %llu, %llu) rel=%llu " "to ready queue at %llu\n", new->comm, new->pid, get_exec_cost(new), get_rt_period(new), get_rt_relative_deadline(new), get_release(new), litmus_clock()); BUG_ON(bheap_node_in_heap(tsk_rt(new)->heap_node)); bheap_insert(rt->order, &rt->ready_queue, tsk_rt(new)->heap_node); rt->check_resched(rt); } /* merge_ready - Add a sorted set of tasks to the rt ready queue. They must be runnable. * @tasks - the newly released tasks */ void __merge_ready(rt_domain_t* rt, struct bheap* tasks) { bheap_union(rt->order, &rt->ready_queue, tasks); rt->check_resched(rt); } #ifdef CONFIG_RELEASE_MASTER void __add_release_on(rt_domain_t* rt, struct task_struct *task, int target_cpu) { TRACE_TASK(task, "add_release_on(), rel=%llu, target=%d\n", get_release(task), target_cpu); list_add(&tsk_rt(task)->list, &rt->tobe_released); task->rt_param.domain = rt; arm_release_timer_on(rt, target_cpu); } #endif /* add_release - add a real-time task to the rt release queue. * @task: the sleeping task */ void __add_release(rt_domain_t* rt, struct task_struct *task) { TRACE_TASK(task, "add_release(), rel=%llu\n", get_release(task)); list_add(&tsk_rt(task)->list, &rt->tobe_released); task->rt_param.domain = rt; arm_release_timer(rt); }