]>
Commit | Line | Data |
---|---|---|
9d7164cf DK |
1 | /**************************************************************************** |
2 | ||
3 | (c) SYSTEC electronic GmbH, D-07973 Greiz, August-Bebel-Str. 29 | |
4 | www.systec-electronic.com | |
5 | ||
6 | Project: openPOWERLINK | |
7 | ||
8 | Description: target specific implementation of | |
9 | high resolution timer module for X86 under Linux | |
10 | The Linux kernel has to be compiled with high resolution | |
11 | timers enabled. This is done by configuring the kernel | |
12 | with CONFIG_HIGH_RES_TIMERS enabled. | |
13 | ||
14 | License: | |
15 | ||
16 | Redistribution and use in source and binary forms, with or without | |
17 | modification, are permitted provided that the following conditions | |
18 | are met: | |
19 | ||
20 | 1. Redistributions of source code must retain the above copyright | |
21 | notice, this list of conditions and the following disclaimer. | |
22 | ||
23 | 2. Redistributions in binary form must reproduce the above copyright | |
24 | notice, this list of conditions and the following disclaimer in the | |
25 | documentation and/or other materials provided with the distribution. | |
26 | ||
27 | 3. Neither the name of SYSTEC electronic GmbH nor the names of its | |
28 | contributors may be used to endorse or promote products derived | |
29 | from this software without prior written permission. For written | |
30 | permission, please contact info@systec-electronic.com. | |
31 | ||
32 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
33 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
34 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
35 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
36 | COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
37 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
38 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
39 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
40 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
41 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
42 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
43 | POSSIBILITY OF SUCH DAMAGE. | |
44 | ||
45 | Severability Clause: | |
46 | ||
47 | If a provision of this License is or becomes illegal, invalid or | |
48 | unenforceable in any jurisdiction, that shall not affect: | |
49 | 1. the validity or enforceability in that jurisdiction of any other | |
50 | provision of this License; or | |
51 | 2. the validity or enforceability in other jurisdictions of that or | |
52 | any other provision of this License. | |
53 | ||
54 | ------------------------------------------------------------------------- | |
55 | ||
56 | $RCSfile: TimerHighReskX86.c,v $ | |
57 | ||
58 | $Author: D.Krueger $ | |
59 | ||
60 | $Revision: 1.4 $ $Date: 2008/04/17 21:38:01 $ | |
61 | ||
62 | $State: Exp $ | |
63 | ||
64 | Build Environment: | |
65 | GNU | |
66 | ||
67 | ------------------------------------------------------------------------- | |
68 | ||
69 | Revision History: | |
70 | ||
71 | ****************************************************************************/ | |
72 | ||
73 | #include "EplInc.h" | |
74 | #include "kernel/EplTimerHighResk.h" | |
75 | #include "Benchmark.h" | |
76 | ||
77 | //#include <linux/config.h> | |
78 | #include <linux/module.h> | |
79 | #include <linux/kernel.h> | |
80 | #include <linux/hrtimer.h> | |
81 | ||
82 | /***************************************************************************/ | |
83 | /* */ | |
84 | /* */ | |
85 | /* G L O B A L D E F I N I T I O N S */ | |
86 | /* */ | |
87 | /* */ | |
88 | /***************************************************************************/ | |
89 | ||
90 | //--------------------------------------------------------------------------- | |
91 | // const defines | |
92 | //--------------------------------------------------------------------------- | |
93 | ||
833dfbe7 GKH |
94 | #define TIMER_COUNT 2 /* max 15 timers selectable */ |
95 | #define TIMER_MIN_VAL_SINGLE 5000 /* min 5us */ | |
96 | #define TIMER_MIN_VAL_CYCLE 100000 /* min 100us */ | |
9d7164cf DK |
97 | |
98 | #define PROVE_OVERRUN | |
99 | ||
9d7164cf | 100 | #ifndef CONFIG_HIGH_RES_TIMERS |
833dfbe7 | 101 | #error "Kernel symbol CONFIG_HIGH_RES_TIMERS is required." |
9d7164cf DK |
102 | #endif |
103 | ||
9d7164cf DK |
104 | // TracePoint support for realtime-debugging |
105 | #ifdef _DBG_TRACE_POINTS_ | |
833dfbe7 GKH |
106 | void PUBLIC TgtDbgSignalTracePoint(BYTE bTracePointNumber_p); |
107 | void PUBLIC TgtDbgPostTraceValue(DWORD dwTraceValue_p); | |
108 | #define TGT_DBG_SIGNAL_TRACE_POINT(p) TgtDbgSignalTracePoint(p) | |
109 | #define TGT_DBG_POST_TRACE_VALUE(v) TgtDbgPostTraceValue(v) | |
9d7164cf | 110 | #else |
833dfbe7 GKH |
111 | #define TGT_DBG_SIGNAL_TRACE_POINT(p) |
112 | #define TGT_DBG_POST_TRACE_VALUE(v) | |
9d7164cf DK |
113 | #endif |
114 | #define HRT_DBG_POST_TRACE_VALUE(Event_p, uiNodeId_p, wErrorCode_p) \ | |
115 | TGT_DBG_POST_TRACE_VALUE((0xE << 28) | (Event_p << 24) \ | |
116 | | (uiNodeId_p << 16) | wErrorCode_p) | |
117 | ||
118 | #define TIMERHDL_MASK 0x0FFFFFFF | |
119 | #define TIMERHDL_SHIFT 28 | |
120 | #define HDL_TO_IDX(Hdl) ((Hdl >> TIMERHDL_SHIFT) - 1) | |
121 | #define HDL_INIT(Idx) ((Idx + 1) << TIMERHDL_SHIFT) | |
122 | #define HDL_INC(Hdl) (((Hdl + 1) & TIMERHDL_MASK) \ | |
123 | | (Hdl & ~TIMERHDL_MASK)) | |
124 | ||
125 | //--------------------------------------------------------------------------- | |
126 | // modul global types | |
127 | //--------------------------------------------------------------------------- | |
128 | ||
833dfbe7 GKH |
129 | typedef struct { |
130 | tEplTimerEventArg m_EventArg; | |
131 | tEplTimerkCallback m_pfnCallback; | |
132 | struct hrtimer m_Timer; | |
133 | BOOL m_fContinuously; | |
134 | unsigned long long m_ullPeriod; | |
9d7164cf DK |
135 | |
136 | } tEplTimerHighReskTimerInfo; | |
137 | ||
833dfbe7 GKH |
138 | typedef struct { |
139 | tEplTimerHighReskTimerInfo m_aTimerInfo[TIMER_COUNT]; | |
9d7164cf DK |
140 | |
141 | } tEplTimerHighReskInstance; | |
142 | ||
143 | //--------------------------------------------------------------------------- | |
144 | // local vars | |
145 | //--------------------------------------------------------------------------- | |
146 | ||
833dfbe7 | 147 | static tEplTimerHighReskInstance EplTimerHighReskInstance_l; |
9d7164cf DK |
148 | |
149 | //--------------------------------------------------------------------------- | |
150 | // local function prototypes | |
151 | //--------------------------------------------------------------------------- | |
152 | ||
833dfbe7 | 153 | enum hrtimer_restart EplTimerHighReskCallback(struct hrtimer *pTimer_p); |
9d7164cf DK |
154 | |
155 | //=========================================================================// | |
156 | // // | |
157 | // P U B L I C F U N C T I O N S // | |
158 | // // | |
159 | //=========================================================================// | |
160 | ||
9d7164cf DK |
161 | //--------------------------------------------------------------------------- |
162 | // | |
163 | // Function: EplTimerHighReskInit() | |
164 | // | |
165 | // Description: initializes the high resolution timer module. | |
166 | // | |
167 | // Parameters: void | |
168 | // | |
169 | // Return: tEplKernel = error code | |
170 | // | |
171 | // State: not tested | |
172 | // | |
173 | //--------------------------------------------------------------------------- | |
174 | ||
175 | tEplKernel PUBLIC EplTimerHighReskInit(void) | |
176 | { | |
833dfbe7 | 177 | tEplKernel Ret; |
9d7164cf | 178 | |
833dfbe7 | 179 | Ret = EplTimerHighReskAddInstance(); |
9d7164cf | 180 | |
833dfbe7 | 181 | return Ret; |
9d7164cf DK |
182 | |
183 | } | |
184 | ||
9d7164cf DK |
185 | //--------------------------------------------------------------------------- |
186 | // | |
187 | // Function: EplTimerHighReskAddInstance() | |
188 | // | |
189 | // Description: initializes the high resolution timer module. | |
190 | // | |
191 | // Parameters: void | |
192 | // | |
193 | // Return: tEplKernel = error code | |
194 | // | |
195 | // State: not tested | |
196 | // | |
197 | //--------------------------------------------------------------------------- | |
198 | ||
199 | tEplKernel PUBLIC EplTimerHighReskAddInstance(void) | |
200 | { | |
833dfbe7 GKH |
201 | tEplKernel Ret; |
202 | unsigned int uiIndex; | |
9d7164cf | 203 | |
833dfbe7 | 204 | Ret = kEplSuccessful; |
9d7164cf | 205 | |
833dfbe7 GKH |
206 | EPL_MEMSET(&EplTimerHighReskInstance_l, 0, |
207 | sizeof(EplTimerHighReskInstance_l)); | |
9d7164cf DK |
208 | |
209 | #ifndef CONFIG_HIGH_RES_TIMERS | |
833dfbe7 GKH |
210 | printk |
211 | ("EplTimerHighResk: Kernel symbol CONFIG_HIGH_RES_TIMERS is required.\n"); | |
212 | Ret = kEplNoResource; | |
213 | return Ret; | |
9d7164cf DK |
214 | #endif |
215 | ||
833dfbe7 GKH |
216 | /* |
217 | * Initialize hrtimer structures for all usable timers. | |
218 | */ | |
219 | for (uiIndex = 0; uiIndex < TIMER_COUNT; uiIndex++) { | |
220 | tEplTimerHighReskTimerInfo *pTimerInfo; | |
221 | struct hrtimer *pTimer; | |
222 | ||
223 | pTimerInfo = &EplTimerHighReskInstance_l.m_aTimerInfo[uiIndex]; | |
224 | pTimer = &pTimerInfo->m_Timer; | |
225 | hrtimer_init(pTimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); | |
226 | ||
227 | pTimer->function = EplTimerHighReskCallback; | |
833dfbe7 GKH |
228 | } |
229 | ||
230 | return Ret; | |
231 | } | |
9d7164cf DK |
232 | |
233 | //--------------------------------------------------------------------------- | |
234 | // | |
235 | // Function: EplTimerHighReskDelInstance() | |
236 | // | |
237 | // Description: shuts down the high resolution timer module. | |
238 | // | |
239 | // Parameters: void | |
240 | // | |
241 | // Return: tEplKernel = error code | |
242 | // | |
243 | // State: not tested | |
244 | // | |
245 | //--------------------------------------------------------------------------- | |
246 | ||
247 | tEplKernel PUBLIC EplTimerHighReskDelInstance(void) | |
248 | { | |
833dfbe7 GKH |
249 | tEplTimerHighReskTimerInfo *pTimerInfo; |
250 | tEplKernel Ret; | |
251 | unsigned int uiIndex; | |
252 | ||
253 | Ret = kEplSuccessful; | |
254 | ||
255 | for (uiIndex = 0; uiIndex < TIMER_COUNT; uiIndex++) { | |
256 | pTimerInfo = &EplTimerHighReskInstance_l.m_aTimerInfo[0]; | |
257 | pTimerInfo->m_pfnCallback = NULL; | |
258 | pTimerInfo->m_EventArg.m_TimerHdl = 0; | |
259 | /* | |
260 | * In this case we can not just try to cancel the timer. | |
261 | * We actually have to wait until its callback function | |
262 | * has returned. | |
263 | */ | |
264 | hrtimer_cancel(&pTimerInfo->m_Timer); | |
265 | } | |
266 | ||
267 | return Ret; | |
9d7164cf DK |
268 | |
269 | } | |
270 | ||
9d7164cf DK |
271 | //--------------------------------------------------------------------------- |
272 | // | |
273 | // Function: EplTimerHighReskModifyTimerNs() | |
274 | // | |
275 | // Description: modifies the timeout of the timer with the specified handle. | |
276 | // If the handle the pointer points to is zero, the timer must | |
277 | // be created first. | |
278 | // If it is not possible to stop the old timer, | |
279 | // this function always assures that the old timer does not | |
280 | // trigger the callback function with the same handle as the new | |
281 | // timer. That means the callback function must check the passed | |
282 | // handle with the one returned by this function. If these are | |
283 | // unequal, the call can be discarded. | |
284 | // | |
285 | // Parameters: pTimerHdl_p = pointer to timer handle | |
286 | // ullTimeNs_p = relative timeout in [ns] | |
287 | // pfnCallback_p = callback function, which is called mutual | |
288 | // exclusive with the Edrv callback functions | |
289 | // (Rx and Tx). | |
290 | // ulArgument_p = user-specific argument | |
291 | // fContinuously_p = if TRUE, callback function will be called | |
292 | // continuously; | |
293 | // otherwise, it is a oneshot timer. | |
294 | // | |
295 | // Return: tEplKernel = error code | |
296 | // | |
297 | // State: not tested | |
298 | // | |
299 | //--------------------------------------------------------------------------- | |
300 | ||
833dfbe7 GKH |
301 | tEplKernel PUBLIC EplTimerHighReskModifyTimerNs(tEplTimerHdl * pTimerHdl_p, |
302 | unsigned long long ullTimeNs_p, | |
303 | tEplTimerkCallback | |
304 | pfnCallback_p, | |
305 | unsigned long ulArgument_p, | |
306 | BOOL fContinuously_p) | |
9d7164cf | 307 | { |
833dfbe7 GKH |
308 | tEplKernel Ret; |
309 | unsigned int uiIndex; | |
310 | tEplTimerHighReskTimerInfo *pTimerInfo; | |
311 | ktime_t RelTime; | |
312 | ||
313 | Ret = kEplSuccessful; | |
314 | ||
315 | // check pointer to handle | |
316 | if (pTimerHdl_p == NULL) { | |
317 | Ret = kEplTimerInvalidHandle; | |
318 | goto Exit; | |
319 | } | |
320 | ||
321 | if (*pTimerHdl_p == 0) { // no timer created yet | |
322 | ||
323 | // search free timer info structure | |
324 | pTimerInfo = &EplTimerHighReskInstance_l.m_aTimerInfo[0]; | |
325 | for (uiIndex = 0; uiIndex < TIMER_COUNT; | |
326 | uiIndex++, pTimerInfo++) { | |
327 | if (pTimerInfo->m_EventArg.m_TimerHdl == 0) { // free structure found | |
328 | break; | |
329 | } | |
330 | } | |
331 | if (uiIndex >= TIMER_COUNT) { // no free structure found | |
332 | Ret = kEplTimerNoTimerCreated; | |
333 | goto Exit; | |
334 | } | |
335 | ||
336 | pTimerInfo->m_EventArg.m_TimerHdl = HDL_INIT(uiIndex); | |
337 | } else { | |
338 | uiIndex = HDL_TO_IDX(*pTimerHdl_p); | |
339 | if (uiIndex >= TIMER_COUNT) { // invalid handle | |
340 | Ret = kEplTimerInvalidHandle; | |
341 | goto Exit; | |
342 | } | |
343 | ||
344 | pTimerInfo = &EplTimerHighReskInstance_l.m_aTimerInfo[uiIndex]; | |
345 | } | |
346 | ||
347 | /* | |
348 | * increment timer handle | |
349 | * (if timer expires right after this statement, the user | |
350 | * would detect an unknown timer handle and discard it) | |
351 | */ | |
352 | pTimerInfo->m_EventArg.m_TimerHdl = | |
353 | HDL_INC(pTimerInfo->m_EventArg.m_TimerHdl); | |
354 | *pTimerHdl_p = pTimerInfo->m_EventArg.m_TimerHdl; | |
355 | ||
356 | // reject too small time values | |
357 | if ((fContinuously_p && (ullTimeNs_p < TIMER_MIN_VAL_CYCLE)) | |
358 | || (!fContinuously_p && (ullTimeNs_p < TIMER_MIN_VAL_SINGLE))) { | |
359 | Ret = kEplTimerNoTimerCreated; | |
360 | goto Exit; | |
361 | } | |
362 | ||
363 | pTimerInfo->m_EventArg.m_ulArg = ulArgument_p; | |
364 | pTimerInfo->m_pfnCallback = pfnCallback_p; | |
365 | pTimerInfo->m_fContinuously = fContinuously_p; | |
366 | pTimerInfo->m_ullPeriod = ullTimeNs_p; | |
367 | ||
368 | /* | |
369 | * HRTIMER_MODE_REL does not influence general handling of this timer. | |
370 | * It only sets relative mode for this start operation. | |
371 | * -> Expire time is calculated by: Now + RelTime | |
372 | * hrtimer_start also skips pending timer events. | |
373 | * The state HRTIMER_STATE_CALLBACK is ignored. | |
374 | * We have to cope with that in our callback function. | |
375 | */ | |
376 | RelTime = ktime_add_ns(ktime_set(0, 0), ullTimeNs_p); | |
377 | hrtimer_start(&pTimerInfo->m_Timer, RelTime, HRTIMER_MODE_REL); | |
378 | ||
379 | Exit: | |
380 | return Ret; | |
9d7164cf DK |
381 | |
382 | } | |
383 | ||
9d7164cf DK |
384 | //--------------------------------------------------------------------------- |
385 | // | |
386 | // Function: EplTimerHighReskDeleteTimer() | |
387 | // | |
388 | // Description: deletes the timer with the specified handle. Afterward the | |
389 | // handle is set to zero. | |
390 | // | |
391 | // Parameters: pTimerHdl_p = pointer to timer handle | |
392 | // | |
393 | // Return: tEplKernel = error code | |
394 | // | |
395 | // State: not tested | |
396 | // | |
397 | //--------------------------------------------------------------------------- | |
398 | ||
833dfbe7 | 399 | tEplKernel PUBLIC EplTimerHighReskDeleteTimer(tEplTimerHdl * pTimerHdl_p) |
9d7164cf | 400 | { |
833dfbe7 GKH |
401 | tEplKernel Ret = kEplSuccessful; |
402 | unsigned int uiIndex; | |
403 | tEplTimerHighReskTimerInfo *pTimerInfo; | |
404 | ||
405 | // check pointer to handle | |
406 | if (pTimerHdl_p == NULL) { | |
407 | Ret = kEplTimerInvalidHandle; | |
408 | goto Exit; | |
409 | } | |
410 | ||
411 | if (*pTimerHdl_p == 0) { // no timer created yet | |
412 | goto Exit; | |
413 | } else { | |
414 | uiIndex = HDL_TO_IDX(*pTimerHdl_p); | |
415 | if (uiIndex >= TIMER_COUNT) { // invalid handle | |
416 | Ret = kEplTimerInvalidHandle; | |
417 | goto Exit; | |
418 | } | |
419 | pTimerInfo = &EplTimerHighReskInstance_l.m_aTimerInfo[uiIndex]; | |
420 | if (pTimerInfo->m_EventArg.m_TimerHdl != *pTimerHdl_p) { // invalid handle | |
421 | goto Exit; | |
422 | } | |
423 | } | |
424 | ||
425 | *pTimerHdl_p = 0; | |
426 | pTimerInfo->m_EventArg.m_TimerHdl = 0; | |
427 | pTimerInfo->m_pfnCallback = NULL; | |
428 | ||
429 | /* | |
430 | * Three return cases of hrtimer_try_to_cancel have to be tracked: | |
431 | * 1 - timer has been removed | |
432 | * 0 - timer was not active | |
433 | * We need not do anything. hrtimer timers just consist of | |
434 | * a hrtimer struct, which we might enqueue in the hrtimers | |
435 | * event list by calling hrtimer_start(). | |
436 | * If a timer is not enqueued, it is not present in hrtimers. | |
437 | * -1 - callback function is running | |
438 | * In this case we have to ensure that the timer is not | |
439 | * continuously restarted. This has been done by clearing | |
440 | * its handle. | |
441 | */ | |
442 | hrtimer_try_to_cancel(&pTimerInfo->m_Timer); | |
443 | ||
444 | Exit: | |
445 | return Ret; | |
9d7164cf DK |
446 | |
447 | } | |
448 | ||
9d7164cf DK |
449 | //--------------------------------------------------------------------------- |
450 | // | |
451 | // Function: EplTimerHighReskCallback() | |
452 | // | |
453 | // Description: Callback function commonly used for all timers. | |
454 | // | |
455 | // Parameters: pTimer_p = pointer to hrtimer | |
456 | // | |
457 | // Return: | |
458 | // | |
459 | // State: not tested | |
460 | // | |
461 | //--------------------------------------------------------------------------- | |
462 | ||
833dfbe7 | 463 | enum hrtimer_restart EplTimerHighReskCallback(struct hrtimer *pTimer_p) |
9d7164cf | 464 | { |
833dfbe7 GKH |
465 | unsigned int uiIndex; |
466 | tEplTimerHighReskTimerInfo *pTimerInfo; | |
467 | tEplTimerHdl OrgTimerHdl; | |
468 | enum hrtimer_restart Ret; | |
469 | ||
470 | BENCHMARK_MOD_24_SET(4); | |
471 | ||
472 | Ret = HRTIMER_NORESTART; | |
473 | pTimerInfo = | |
474 | container_of(pTimer_p, tEplTimerHighReskTimerInfo, m_Timer); | |
475 | uiIndex = HDL_TO_IDX(pTimerInfo->m_EventArg.m_TimerHdl); | |
476 | if (uiIndex >= TIMER_COUNT) { // invalid handle | |
477 | goto Exit; | |
478 | } | |
479 | ||
480 | /* | |
481 | * We store the timer handle before calling the callback function | |
482 | * as the timer can be modified inside it. | |
483 | */ | |
484 | OrgTimerHdl = pTimerInfo->m_EventArg.m_TimerHdl; | |
485 | ||
486 | if (pTimerInfo->m_pfnCallback != NULL) { | |
487 | pTimerInfo->m_pfnCallback(&pTimerInfo->m_EventArg); | |
488 | } | |
489 | ||
490 | if (pTimerInfo->m_fContinuously) { | |
491 | ktime_t Interval; | |
9d7164cf | 492 | #ifdef PROVE_OVERRUN |
833dfbe7 GKH |
493 | ktime_t Now; |
494 | unsigned long Overruns; | |
9d7164cf DK |
495 | #endif |
496 | ||
833dfbe7 GKH |
497 | if (OrgTimerHdl != pTimerInfo->m_EventArg.m_TimerHdl) { |
498 | /* modified timer has already been restarted */ | |
499 | goto Exit; | |
500 | } | |
9d7164cf | 501 | #ifdef PROVE_OVERRUN |
833dfbe7 GKH |
502 | Now = ktime_get(); |
503 | Interval = | |
504 | ktime_add_ns(ktime_set(0, 0), pTimerInfo->m_ullPeriod); | |
505 | Overruns = hrtimer_forward(pTimer_p, Now, Interval); | |
506 | if (Overruns > 1) { | |
507 | printk | |
508 | ("EplTimerHighResk: Continuous timer (handle 0x%lX) had to skip %lu interval(s)!\n", | |
509 | pTimerInfo->m_EventArg.m_TimerHdl, Overruns - 1); | |
510 | } | |
9d7164cf | 511 | #else |
833dfbe7 GKH |
512 | pTimer_p->expires = ktime_add_ns(pTimer_p->expires, |
513 | pTimerInfo->m_ullPeriod); | |
9d7164cf DK |
514 | #endif |
515 | ||
833dfbe7 GKH |
516 | Ret = HRTIMER_RESTART; |
517 | } | |
9d7164cf | 518 | |
833dfbe7 GKH |
519 | Exit: |
520 | BENCHMARK_MOD_24_RESET(4); | |
521 | return Ret; | |
9d7164cf | 522 | } |