summaryrefslogtreecommitdiffstats
path: root/pk/kernel/pk_timer_core.c
blob: 59b9d6286d9ad59a42d105756ad28d20ee8ab6e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
//-----------------------------------------------------------------------------
// *! (C) Copyright International Business Machines Corp. 2014
// *! All Rights Reserved -- Property of IBM
// *! *** IBM Confidential ***
//-----------------------------------------------------------------------------

/// \file pk_timer_core.c
/// \brief PK portable kernel timer handler
///
/// This file contains core routines that would be needed by any application
/// that requires PK timer support at runtime.
///
/// PK implements a 'tickless' kernel - all events are scheduled at absolute
/// times of the PK timebase.  This approach gives the application full
/// control over granularity of event scheduling.  Scheduling in absolute time
/// opens up the possibility of scheduling events "in the past".  PK
/// uniformly handles this case by scheduling "past" events to occur 1
/// timebase tick in the future, so that timer callbacks are always run in the
/// expected interrupt context.
///
/// PK implements the time queue as a simple unordered list of events, plus a
/// dedicated variable that holds the earliest timeout of any event in the
/// list.  This is thought to be an appropriate data structure for the
/// following reasons:
///
/// - PK applications will be small and will not schedule a large number of
/// events.  Therefore the cost of scanning the list each time an event times
/// out is balanced against the cost of maintaining the list as a sorted data
/// structure each time an event is added or removed from the event queue.
///
/// - PK applications may schedule and cancel many, many more events (safety
/// timeouts) than are ever allowed to expire. Events can be added and deleted
/// from the simple DEQUE very quickly since there is no sorting
/// overhead.
///
/// Events are added to the queue simply by placing them at the end of the
/// queue.  If the new event times out earlier than the previous earliest
/// event, the hardware timeout is rescheduled for the new event time.  Events
/// are deleted from the queue (cancelled) simply by deleting them.  Deletion
/// does not affect the hardware timeout, even if the deleted event would have
/// been the next to time out.  It is not an error for the timer handler to
/// take a timer interrupt and find no events pending.  Pending events can
/// also be rescheduled in place.
///
/// When a timeout occurs the event list is scanned from the beginning, and
/// any event that has timed out has its callback processed.
/// Since event and callback processing take
/// time, the list is potentially scanned multiple times until there are no
/// more timed-out events in the list.
///
/// Note that callbacks are not necessarily processed in time-order.  In this
/// sense the PK time queue is like a traditional tick-based time queue in
/// that events are effectively lumped into groups of events that time out
/// together.  In a tick-based kernel the 'lump' is the tick interval; here
/// the 'lump' is a variable interval that corresponds to the time it takes to
/// process the entire event list.
///
/// Timer callbacks are typically run with interrupt preemption enabled.
/// Special callbacks may run without preemption.  This is the only part of
/// the PK kernel where data structures of indeterminate size are processed.
/// During processing of the event list by the timer interrupt handler, the
/// consideration of each event always includes a window of preemptability.

#define __PK_TIMER_CORE_C__

#include "pk.h"

// Declare the timer bottom half handler
static PK_BH_HANDLER(__pk_timer_bh_handler);

// Define the timer bottom half handler that the interrupt handler will
// schedule
PK_BH_STATIC_CREATE(pk_timer_bh, __pk_timer_bh_handler, 0);


// This routine is only used in this file, and will always be called in a
// critical section.

static inline int
timer_active(PkTimer* timer)
{
    return pk_deque_is_queued((PkDeque*)timer);
}


// This is the kernel version of pk_timer_cancel().
//
// This routine is used here and by thread and semaphore routines.
// External interrupts must be disabled at entry.
//
// If the timer is active, then there is a special case if we are going to
// delete the 'cursor' - that is the timer that __pk_timer_handler() is going
// to handle next.  In this case we need to move the cursor to the next timer
// in the queue.
//
//  Note that cancelling a timer does not cause a re-evaluation of the next
//  timeout.  This will happen naturally when the current timeout expires.

int
__pk_timer_cancel(PkTimer *timer)
{
    int rc;
    PkDeque* timer_deque = (PkDeque*)timer;
    PkTimeQueue* tq = &__pk_time_queue;

    if (!timer_active(timer)) {

        rc = -PK_TIMER_NOT_ACTIVE;

    } else {

        if (timer_deque == tq->cursor) {
            tq->cursor = tq->cursor->next;
        }
        pk_deque_delete(timer_deque);
        rc = 0;
    }
    return rc;
}


// This is the kernel version of pk_timer_schedule().
//
// This routine is used here and by thread and semaphore routines.
// interrupts must be disabled at entry.
//
// Unless the timer is already active it is enqueued in the doubly-linked
// timer list by inserting the timer at the end of the queue. Then the
// hardware timeout is scheduled if necessary. If the time queue 'cursor' != 0
// we are in the midst of processing the time queue, and the end of time queue
// processing will schedule the next hardware timemout.

void
__pk_timer_schedule(PkTimer* timer)
{
    PkTimeQueue* tq = &__pk_time_queue;

    if (!timer_active(timer)) {
        pk_deque_push_back((PkDeque*)tq, (PkDeque*)timer);
    }

    if (timer->timeout < tq->next_timeout) {
        tq->next_timeout = timer->timeout;
        if (tq->cursor == 0) {
            __pk_schedule_hardware_timeout(tq->next_timeout);
        }
    }
}
        

// The tickless timer mechanism has timed out.  Note that due to timer
// deletions and other factors, there may not actually be a timer in the queue
// that has timed out - but it doesn't matter (other than for efficiency).
//
// This routine must not be entered reentrantly. 
//
// First, time out any timers that have expired.  Timers in the queue are
// unordered, so we have to check every one.  Since passing through the
// loop takes time, we may have to make multiple passes until we know
// that there are no timers in the queue that have already timed
// out. Note that it would also work to only go through the loop once and
// let the hardware scheduler take care of looping, but that would imply
// more overhead than the current implementation.
//
// On each pass through the loop tq->next_timeout computes the minimum timeout
// of events remaining in the queue.  This is the only part of the kernel that
// searches a list of indefinite length. Kernel interrupt latency is mitigated
// by running this function as a bottom half.  As such, interrupts are only
// disabled when explicitly requested.
//
// Because interrupt preemption is enabled during processing, and preempting
// handlers may invoke time queue operations, we need to establish a pointer
// to the next entry to be examined (tq->cursor) before enabling interupts.
// It's possible that this pointer will be changed by other interrupt handlers
// that cancel the timer pointed to by tq->cursor. 
//
// The main loop iterates on the PkDeque form of the time queue, casting each
// element back up to the PkTimer as it is processed.

static void
__pk_timer_bh_handler(void* arg)
{
    PkMachineContext ctx;
    PkTimeQueue* tq;
    PkTimebase now;
    PkTimer* timer;
    PkDeque* timer_deque;
    PkTimerCallback callback;

    tq = &__pk_time_queue;

    // Check if we entered the function while it was running in another context.
    if (PK_ERROR_CHECK_KERNEL) {
        if (tq->cursor != 0) {
            PK_PANIC(PK_TIMER_HANDLER_INVARIANT);
        }
    }

    pk_critical_section_enter(&ctx);

    while ((now = pk_timebase_get()) >= tq->next_timeout) {
        tq->next_timeout = PK_TIMEBASE_MAX;
        timer_deque = ((PkDeque*)tq)->next;

        // Iterate through the entire timer list, calling the callback of
        // timed-out elements and finding the timer that will timeout next,
        // which is stored in tq->next_timeout.
        while (timer_deque != (PkDeque*)tq) {
    
            timer = (PkTimer*)timer_deque;

            // Setting this to a non-zero value indicates we are in the middle
            // of processing the time queue.
            tq->cursor = timer_deque->next;

            if (timer->timeout <= now) {

                // The timer timed out.  It is removed from the queue.
                //
                // The callback may be made with interrupt preemption enabled
                // or disabled.  However to mitigate kernel interrupt latency
                // we go ahead and open up to interrupts after the callback if
                // the callback itself was not preemptible.

                pk_deque_delete(timer_deque);

                pk_critical_section_exit(&ctx);

                callback = timer->callback;
                if (callback) {
                    callback(timer->arg);
                }                        

            } else {

                // This timer has not timed out.  Its timeout will simply
                // participate in the computation of the next timeout.
                tq->next_timeout = MIN(timer->timeout, tq->next_timeout);
                pk_critical_section_exit(&ctx);
            }

            timer_deque = tq->cursor;
            pk_critical_section_enter(&ctx);
        }

        // Time has passed since we checked the time.  Loop back
        // to check the time again and see if enough time has passed
        // that the next timer has timed out too.
    }
    
    pk_critical_section_exit(&ctx);

    // This marks that we are no longer processing the time queue
    tq->cursor = 0;

    // Finally, reschedule the next timeout
    __pk_schedule_hardware_timeout(tq->next_timeout);
}


void
__pk_timer_handler(void)
{
    //schedule the timer bottom half handler which
    //is preemptible.
    pk_bh_schedule(&pk_timer_bh);
}


/// Schedule a timer for an interval relative to the current time.
///
/// \param timer The PkTimer to schedule.
///
/// \param interval The timer will be scheduled to time out at the current
/// time (pk_timebase_get()) plus this \a interval.
///
/// Once created with pk_timer_create() a timer can be \e scheduled, which
/// queues the timer in the kernel time queue.  It is not an error to call \c
/// pk_timer_schedule() on a timer that is already scheduled in the time
/// queue - the timer is simply rescheduled with the new characteristics.
///
/// Return values other than PK_OK (0) are errors; see \ref pk_errors
///
/// \retval 0 Successful completion
///
/// \retval -PK_INVALID_TIMER_AT_SCHEDULE A a null (0) pointer was provided as 
/// the \a timer argument.
///

int
pk_timer_schedule(PkTimer    *timer, 
                   PkInterval interval)
{
    PkMachineContext ctx;
    PkTimebase  timeout = pk_timebase_get() + PK_INTERVAL_SCALE(interval);

    pk_critical_section_enter(&ctx);

    if (PK_ERROR_CHECK_API) {
        PK_ERROR_IF(timer == 0, PK_INVALID_TIMER_AT_SCHEDULE);
    }

    timer->timeout = timeout;
    __pk_timer_schedule(timer);

    pk_critical_section_exit(&ctx);

    return PK_OK;
}


/// Cancel (dequeue) a timer.
///
/// \param timer The PkTimer to cancel.
///
/// Timers can be canceled at any time.  It is never an error to call 
/// pk_timer_cancel() on an PkTimer object after it is created. Memory used
/// by an PkTimer can be safely reused for another purpose after a successful
/// call ofpk_timer_cancel().
///
/// Return values other than PK_OK (0) are not necessarily errors; see \ref
/// pk_errors 
///
/// The following return codes are non-error codes:
///
/// \retval 0 Successful completion
///
/// \retval -PK_TIMER_NOT_ACTIVE The \a timer is not currently scheduled,
/// i.e. it was never scheduled or has timed out.  This code is returned for
/// information only and is not considered an error.
///
/// The following return codes are  error codes:
///
/// \retval -PK_INVALID_TIMER_AT_CANCEL The \a timer is a null (0) pointer.
///

int
pk_timer_cancel(PkTimer *timer)
{
    PkMachineContext ctx;
    int rc = PK_OK;

    if (PK_ERROR_CHECK_API) {
        PK_ERROR_IF(timer == 0, PK_INVALID_TIMER_AT_CANCEL);
    }

    pk_critical_section_enter(&ctx);

    rc = __pk_timer_cancel(timer);

    pk_critical_section_exit(&ctx);

    return rc;
}


/// Get information about a timer.
///
/// \param timer The PkTimer to query
///
/// \param timeout The API returns the absolute timeout of the timer through
/// this pointer.  If the timer is active, this is the current timeout.  If
/// the timer has timed out then this is the previous absolute timeout.  If
/// the timer was never scheduled this will be 0.  The caller can set this
/// parameter to the null pointer (0) if this information is not required.
///
/// \param active If the value returned through this pointer is 1 then the
/// timer is active (currently scheduled), otherwise the value will be 0
/// indicating an inactive timer. The caller can set this parameter to the
/// null pointer (0) if this information is not required.
///
/// The information returned by this API can only be guaranteed consistent if
/// the API is called from a critical section.
///
/// Return values other than PK_OK (0) are errors; see \ref pk_errors
///
/// \retval 0 Successful completion
///
/// \retval -PK_INVALID_TIMER_AT_INFO The \a timer is a null (0) pointer.

int
pk_timer_info_get(PkTimer    *timer,
                   PkTimebase *timeout,
                   int         *active)
                   
{
    if (PK_ERROR_CHECK_API) {
        PK_ERROR_IF(timer == 0, PK_INVALID_TIMER_AT_INFO);
    }

    if (timeout) {
        *timeout = timer->timeout;
    }
    if (active) {
        *active = timer_active(timer);
    }

    return PK_OK;
}

#undef __PK_TIMER_CORE_C__
OpenPOWER on IntegriCloud