]> git.proxmox.com Git - qemu.git/blame - include/qemu/timer.h
aio / timers: Add QEMUTimerListGroup to AioContext
[qemu.git] / include / qemu / timer.h
CommitLineData
87ecb68b
PB
1#ifndef QEMU_TIMER_H
2#define QEMU_TIMER_H
3
ff83c66e 4#include "qemu/typedefs.h"
29e922b6 5#include "qemu-common.h"
1de7afc9 6#include "qemu/notify.h"
29e922b6 7
87ecb68b
PB
8/* timers */
9
0ce1b948
PB
10#define SCALE_MS 1000000
11#define SCALE_US 1000
12#define SCALE_NS 1
13
ff83c66e
AB
14/**
15 * QEMUClockType:
16 *
17 * The following clock types are available:
18 *
19 * @QEMU_CLOCK_REALTIME: Real time clock
20 *
21 * The real time clock should be used only for stuff which does not
22 * change the virtual machine state, as it is run even if the virtual
23 * machine is stopped. The real time clock has a frequency of 1000
24 * Hz.
25 *
26 * Formerly rt_clock
27 *
28 * @QEMU_CLOCK_VIRTUAL: virtual clock
29 *
30 * The virtual clock is only run during the emulation. It is stopped
31 * when the virtual machine is stopped. Virtual timers use a high
32 * precision clock, usually cpu cycles (use ticks_per_sec).
33 *
34 * Formerly vm_clock
35 *
36 * @QEMU_CLOCK_HOST: host clock
37 *
38 * The host clock should be use for device models that emulate accurate
39 * real time sources. It will continue to run when the virtual machine
40 * is suspended, and it will reflect system time changes the host may
41 * undergo (e.g. due to NTP). The host clock has the same precision as
42 * the virtual clock.
43 *
44 * Formerly host_clock
45 */
46
47typedef enum {
48 QEMU_CLOCK_REALTIME = 0,
49 QEMU_CLOCK_VIRTUAL = 1,
50 QEMU_CLOCK_HOST = 2,
51 QEMU_CLOCK_MAX
52} QEMUClockType;
58ac56b9 53
87ecb68b 54typedef struct QEMUClock QEMUClock;
ff83c66e 55typedef struct QEMUTimerList QEMUTimerList;
754d6a54
AB
56
57struct QEMUTimerListGroup {
58 QEMUTimerList *tl[QEMU_CLOCK_MAX];
59};
60
87ecb68b
PB
61typedef void QEMUTimerCB(void *opaque);
62
ff83c66e
AB
63struct QEMUTimer {
64 int64_t expire_time; /* in nanoseconds */
65 QEMUTimerList *timer_list;
66 QEMUTimerCB *cb;
67 void *opaque;
68 QEMUTimer *next;
69 int scale;
70};
71
754d6a54 72extern QEMUTimerListGroup main_loop_tlg;
ff83c66e 73extern QEMUClock *qemu_clocks[QEMU_CLOCK_MAX];
87ecb68b 74
ff83c66e
AB
75/**
76 * qemu_clock_ptr:
77 * @type: type of clock
78 *
79 * Translate a clock type into a pointer to QEMUClock object.
80 *
81 * Returns: a pointer to the QEMUClock object
82 */
83static inline QEMUClock *qemu_clock_ptr(QEMUClockType type)
84{
85 return qemu_clocks[type];
86}
87ecb68b 87
ff83c66e
AB
88/* These three clocks are maintained here with separate variable
89 * names for compatibility only.
90 */
91#define rt_clock (qemu_clock_ptr(QEMU_CLOCK_REALTIME))
92#define vm_clock (qemu_clock_ptr(QEMU_CLOCK_VIRTUAL))
93#define host_clock (qemu_clock_ptr(QEMU_CLOCK_HOST))
21d5d12b 94
41c872b6 95int64_t qemu_get_clock_ns(QEMUClock *clock);
ff83c66e
AB
96bool qemu_clock_has_timers(QEMUClock *clock);
97bool qemu_clock_expired(QEMUClock *clock);
dc2dfcf0 98int64_t qemu_clock_deadline(QEMUClock *clock);
02a03a9f
AB
99
100/**
101 * qemu_clock_deadline_ns:
102 * @clock: the clock to operate on
103 *
104 * Calculate the timeout of the earliest expiring timer
105 * in nanoseconds, or -1 if no timer is set to expire.
106 *
107 * Returns: time until expiry in nanoseconds or -1
108 */
109int64_t qemu_clock_deadline_ns(QEMUClock *clock);
110
ff83c66e
AB
111/**
112 * qemu_clock_use_for_deadline:
113 * @clock: the clock to operate on
114 *
115 * Determine whether a clock should be used for deadline
116 * calculations. Some clocks, for instance vm_clock with
117 * use_icount set, do not count in nanoseconds. Such clocks
118 * are not used for deadline calculations, and are presumed
119 * to interrupt any poll using qemu_notify/aio_notify
120 * etc.
121 *
122 * Returns: true if the clock runs in nanoseconds and
123 * should be used for a deadline.
124 */
125bool qemu_clock_use_for_deadline(QEMUClock *clock);
126
127/**
128 * qemu_clock_get_main_loop_timerlist:
129 * @clock: the clock to operate on
130 *
131 * Return the default timer list assocatiated with a clock.
132 *
133 * Returns: the default timer list
134 */
135QEMUTimerList *qemu_clock_get_main_loop_timerlist(QEMUClock *clock);
136
137/**
138 * timerlist_new:
139 * @type: the clock type to associate with the timerlist
140 *
141 * Create a new timerlist associated with the clock of
142 * type @type.
143 *
144 * Returns: a pointer to the QEMUTimerList created
145 */
146QEMUTimerList *timerlist_new(QEMUClockType type);
147
148/**
149 * timerlist_free:
150 * @timer_list: the timer list to free
151 *
152 * Frees a timer_list. It must have no active timers.
153 */
154void timerlist_free(QEMUTimerList *timer_list);
155
156/**
157 * timerlist_has_timers:
158 * @timer_list: the timer list to operate on
159 *
160 * Determine whether a timer list has active timers
161 *
162 * Returns: true if the timer list has timers.
163 */
164bool timerlist_has_timers(QEMUTimerList *timer_list);
165
166/**
167 * timerlist_expired:
168 * @timer_list: the timer list to operate on
169 *
170 * Determine whether a timer list has any timers which
171 * are expired.
172 *
173 * Returns: true if the timer list has timers which
174 * have expired.
175 */
176bool timerlist_expired(QEMUTimerList *timer_list);
177
178/**
179 * timerlist_deadline:
180 * @timer_list: the timer list to operate on
181 *
182 * Determine the deadline for a timer_list. This is
183 * a legacy function which returns INT32_MAX if the
184 * timer list has no timers or if the earliest timer
185 * expires later than INT32_MAX nanoseconds away.
186 *
187 * Returns: the number of nanoseconds until the earliest
188 * timer expires or INT32_MAX in the situations listed
189 * above
190 */
191int64_t timerlist_deadline(QEMUTimerList *timer_list);
192
193/**
194 * timerlist_deadline_ns:
195 * @timer_list: the timer list to operate on
196 *
197 * Determine the deadline for a timer_list, i.e.
198 * the number of nanoseconds until the first timer
199 * expires. Return -1 if there are no timers.
200 *
201 * Returns: the number of nanoseconds until the earliest
202 * timer expires -1 if none
203 */
204int64_t timerlist_deadline_ns(QEMUTimerList *timer_list);
205
206/**
207 * timerlist_getclock:
208 * @timer_list: the timer list to operate on
209 *
210 * Determine the clock associated with a timer list.
211 *
212 * Returns: the clock associated with the timer list.
213 */
214QEMUClock *timerlist_get_clock(QEMUTimerList *timer_list);
215
216/**
217 * timerlist_run_timers:
218 * @timer_list: the timer list to use
219 *
220 * Call all expired timers associated with the timer list.
221 *
222 * Returns: true if any timer expired
223 */
224bool timerlist_run_timers(QEMUTimerList *timer_list);
225
754d6a54
AB
226/**
227 * timerlistgroup_init:
228 * @tlg: the timer list group
229 *
230 * Initialise a timer list group. This must already be
231 * allocated in memory and zeroed.
232 */
233void timerlistgroup_init(QEMUTimerListGroup *tlg);
234
235/**
236 * timerlistgroup_deinit:
237 * @tlg: the timer list group
238 *
239 * Deinitialise a timer list group. This must already be
240 * initialised. Note the memory is not freed.
241 */
242void timerlistgroup_deinit(QEMUTimerListGroup *tlg);
243
244/**
245 * timerlistgroup_run_timers:
246 * @tlg: the timer list group
247 *
248 * Run the timers associated with a timer list group.
249 * This will run timers on multiple clocks.
250 *
251 * Returns: true if any timer callback ran
252 */
253bool timerlistgroup_run_timers(QEMUTimerListGroup *tlg);
254
255/**
256 * timerlistgroup_deadline_ns
257 * @tlg: the timer list group
258 *
259 * Determine the deadline of the soonest timer to
260 * expire associated with any timer list linked to
261 * the timer list group. Only clocks suitable for
262 * deadline calculation are included.
263 *
264 * Returns: the deadline in nanoseconds or -1 if no
265 * timers are to expire.
266 */
267int64_t timerlistgroup_deadline_ns(QEMUTimerListGroup *tlg);
268
02a03a9f
AB
269/**
270 * qemu_timeout_ns_to_ms:
271 * @ns: nanosecond timeout value
272 *
273 * Convert a nanosecond timeout value (or -1) to
274 * a millisecond value (or -1), always rounding up.
275 *
276 * Returns: millisecond timeout value
277 */
278int qemu_timeout_ns_to_ms(int64_t ns);
279
4e0c6529
AB
280/**
281 * qemu_poll_ns:
282 * @fds: Array of file descriptors
283 * @nfds: number of file descriptors
284 * @timeout: timeout in nanoseconds
285 *
286 * Perform a poll like g_poll but with a timeout in nanoseconds.
287 * See g_poll documentation for further details.
288 *
289 * Returns: number of fds ready
290 */
291int qemu_poll_ns(GPollFD *fds, guint nfds, int64_t timeout);
5e1ec7b2 292void qemu_clock_enable(QEMUClock *clock, bool enabled);
ab33fcda 293void qemu_clock_warp(QEMUClock *clock);
87ecb68b 294
691a0c9c
JK
295void qemu_register_clock_reset_notifier(QEMUClock *clock, Notifier *notifier);
296void qemu_unregister_clock_reset_notifier(QEMUClock *clock,
297 Notifier *notifier);
298
4a998740
PB
299QEMUTimer *qemu_new_timer(QEMUClock *clock, int scale,
300 QEMUTimerCB *cb, void *opaque);
ff83c66e
AB
301
302/**
303 * timer_init:
304 * @ts: the timer to be initialised
305 * @timer_list: the timer list to attach the timer to
306 * @scale: the scale value for the tiemr
307 * @cb: the callback to be called when the timer expires
308 * @opaque: the opaque pointer to be passed to the callback
309 *
310 * Initialise a new timer and associate it with @timer_list.
311 * The caller is responsible for allocating the memory.
312 *
313 * You need not call an explicit deinit call. Simply make
314 * sure it is not on a list with timer_del.
315 */
316void timer_init(QEMUTimer *ts,
317 QEMUTimerList *timer_list, int scale,
318 QEMUTimerCB *cb, void *opaque);
319
320/**
321 * timer_new_tl:
322 * @timer_list: the timer list to attach the timer to
323 * @scale: the scale value for the tiemr
324 * @cb: the callback to be called when the timer expires
325 * @opaque: the opaque pointer to be passed to the callback
326 *
327 * Creeate a new timer and associate it with @timer_list.
328 * The memory is allocated by the function.
329 *
330 * This is not the preferred interface unless you know you
331 * are going to call timer_free. Use timer_init instead.
332 *
333 * Returns: a pointer to the timer
334 */
335static inline QEMUTimer *timer_new_tl(QEMUTimerList *timer_list,
336 int scale,
337 QEMUTimerCB *cb,
338 void *opaque)
339{
340 QEMUTimer *ts = g_malloc0(sizeof(QEMUTimer));
341 timer_init(ts, timer_list, scale, cb, opaque);
342 return ts;
343}
344
87ecb68b
PB
345void qemu_free_timer(QEMUTimer *ts);
346void qemu_del_timer(QEMUTimer *ts);
2ff68d07 347void qemu_mod_timer_ns(QEMUTimer *ts, int64_t expire_time);
87ecb68b 348void qemu_mod_timer(QEMUTimer *ts, int64_t expire_time);
e93379b0
AB
349bool timer_pending(QEMUTimer *ts);
350bool timer_expired(QEMUTimer *timer_head, int64_t current_time);
351uint64_t timer_expire_time_ns(QEMUTimer *ts);
87ecb68b 352
ff83c66e
AB
353/* New format calling conventions for timers */
354
355/**
356 * timer_free:
357 * @ts: the timer
358 *
359 * Free a timer (it must not be on the active list)
360 */
361static inline void timer_free(QEMUTimer *ts)
362{
363 qemu_free_timer(ts);
364}
365
366/**
367 * timer_del:
368 * @ts: the timer
369 *
370 * Delete a timer from the active list.
371 */
372static inline void timer_del(QEMUTimer *ts)
373{
374 qemu_del_timer(ts);
375}
376
377/**
378 * timer_mod_ns:
379 * @ts: the timer
380 * @expire_time: the expiry time in nanoseconds
381 *
382 * Modify a timer to expire at @expire_time
383 */
384static inline void timer_mod_ns(QEMUTimer *ts, int64_t expire_time)
385{
386 qemu_mod_timer_ns(ts, expire_time);
387}
388
389/**
390 * timer_mod:
391 * @ts: the timer
392 * @expire_time: the expire time in the units associated with the timer
393 *
394 * Modify a timer to expiry at @expire_time, taking into
395 * account the scale associated with the timer.
396 */
397static inline void timer_mod(QEMUTimer *ts, int64_t expire_timer)
398{
399 qemu_mod_timer(ts, expire_timer);
400}
401
f9a976b7
AB
402/**
403 * qemu_run_timers:
404 * @clock: clock on which to operate
405 *
ff83c66e
AB
406 * Run all the timers associated with the default timer list
407 * of a clock.
f9a976b7
AB
408 *
409 * Returns: true if any timer ran.
410 */
411bool qemu_run_timers(QEMUClock *clock);
412
413/**
414 * qemu_run_all_timers:
415 *
ff83c66e
AB
416 * Run all the timers associated with the default timer list
417 * of every clock.
f9a976b7
AB
418 *
419 * Returns: true if any timer ran.
420 */
421bool qemu_run_all_timers(void);
422
db1a4972 423void configure_alarms(char const *opt);
db1a4972
PB
424void init_clocks(void);
425int init_timer_alarm(void);
db1a4972 426
70c3b557
BS
427int64_t cpu_get_ticks(void);
428void cpu_enable_ticks(void);
429void cpu_disable_ticks(void);
430
02a03a9f
AB
431/**
432 * qemu_soonest_timeout:
433 * @timeout1: first timeout in nanoseconds (or -1 for infinite)
434 * @timeout2: second timeout in nanoseconds (or -1 for infinite)
435 *
436 * Calculates the soonest of two timeout values. -1 means infinite, which
437 * is later than any other value.
438 *
439 * Returns: soonest timeout value in nanoseconds (or -1 for infinite)
440 */
441static inline int64_t qemu_soonest_timeout(int64_t timeout1, int64_t timeout2)
442{
443 /* we can abuse the fact that -1 (which means infinite) is a maximal
444 * value when cast to unsigned. As this is disgusting, it's kept in
445 * one inline function.
446 */
447 return ((uint64_t) timeout1 < (uint64_t) timeout2) ? timeout1 : timeout2;
448}
449
ff83c66e
AB
450/**
451 * qemu_new_timer_ns:
452 * @clock: the clock to associate with the timer
453 * @callback: the callback to call when the timer expires
454 * @opaque: the opaque pointer to pass to the callback
455 *
456 * Create a new timer with nanosecond scale on the default timer list
457 * associated with the clock.
458 *
459 * Returns: a pointer to the newly created timer
460 */
0ce1b948
PB
461static inline QEMUTimer *qemu_new_timer_ns(QEMUClock *clock, QEMUTimerCB *cb,
462 void *opaque)
463{
4a998740 464 return qemu_new_timer(clock, SCALE_NS, cb, opaque);
0ce1b948
PB
465}
466
ff83c66e
AB
467/**
468 * qemu_new_timer_us:
469 * @clock: the clock to associate with the timer
470 * @callback: the callback to call when the timer expires
471 * @opaque: the opaque pointer to pass to the callback
472 *
473 * Create a new timer with microsecond scale on the default timer list
474 * associated with the clock.
475 *
476 * Returns: a pointer to the newly created timer
477 */
478static inline QEMUTimer *qemu_new_timer_us(QEMUClock *clock,
479 QEMUTimerCB *cb,
480 void *opaque)
481{
482 return qemu_new_timer(clock, SCALE_US, cb, opaque);
483}
484
485/**
486 * qemu_new_timer_ms:
487 * @clock: the clock to associate with the timer
488 * @callback: the callback to call when the timer expires
489 * @opaque: the opaque pointer to pass to the callback
490 *
491 * Create a new timer with millisecond scale on the default timer list
492 * associated with the clock.
493 *
494 * Returns: a pointer to the newly created timer
495 */
496static inline QEMUTimer *qemu_new_timer_ms(QEMUClock *clock,
497 QEMUTimerCB *cb,
0ce1b948
PB
498 void *opaque)
499{
4a998740 500 return qemu_new_timer(clock, SCALE_MS, cb, opaque);
0ce1b948
PB
501}
502
503static inline int64_t qemu_get_clock_ms(QEMUClock *clock)
504{
505 return qemu_get_clock_ns(clock) / SCALE_MS;
506}
507
274dfed8
AL
508static inline int64_t get_ticks_per_sec(void)
509{
510 return 1000000000LL;
511}
87ecb68b 512
c57c846a
BS
513/* real time host monotonic timer */
514static inline int64_t get_clock_realtime(void)
515{
516 struct timeval tv;
517
518 gettimeofday(&tv, NULL);
519 return tv.tv_sec * 1000000000LL + (tv.tv_usec * 1000);
520}
521
522/* Warning: don't insert tracepoints into these functions, they are
523 also used by simpletrace backend and tracepoints would cause
524 an infinite recursion! */
525#ifdef _WIN32
526extern int64_t clock_freq;
527
528static inline int64_t get_clock(void)
529{
530 LARGE_INTEGER ti;
531 QueryPerformanceCounter(&ti);
532 return muldiv64(ti.QuadPart, get_ticks_per_sec(), clock_freq);
533}
534
535#else
536
537extern int use_rt_clock;
538
539static inline int64_t get_clock(void)
540{
d05ef160 541#ifdef CLOCK_MONOTONIC
c57c846a
BS
542 if (use_rt_clock) {
543 struct timespec ts;
544 clock_gettime(CLOCK_MONOTONIC, &ts);
545 return ts.tv_sec * 1000000000LL + ts.tv_nsec;
546 } else
547#endif
548 {
549 /* XXX: using gettimeofday leads to problems if the date
550 changes, so it should be avoided. */
551 return get_clock_realtime();
552 }
553}
554#endif
db1a4972 555
87ecb68b
PB
556void qemu_get_timer(QEMUFile *f, QEMUTimer *ts);
557void qemu_put_timer(QEMUFile *f, QEMUTimer *ts);
558
29e922b6 559/* icount */
29e922b6 560int64_t cpu_get_icount(void);
946fb27c 561int64_t cpu_get_clock(void);
29e922b6
BS
562
563/*******************************************/
564/* host CPU ticks (if available) */
565
566#if defined(_ARCH_PPC)
567
568static inline int64_t cpu_get_real_ticks(void)
569{
570 int64_t retval;
571#ifdef _ARCH_PPC64
572 /* This reads timebase in one 64bit go and includes Cell workaround from:
573 http://ozlabs.org/pipermail/linuxppc-dev/2006-October/027052.html
574 */
575 __asm__ __volatile__ ("mftb %0\n\t"
576 "cmpwi %0,0\n\t"
577 "beq- $-8"
578 : "=r" (retval));
579#else
580 /* http://ozlabs.org/pipermail/linuxppc-dev/1999-October/003889.html */
581 unsigned long junk;
4a9590f3
AG
582 __asm__ __volatile__ ("mfspr %1,269\n\t" /* mftbu */
583 "mfspr %L0,268\n\t" /* mftb */
584 "mfspr %0,269\n\t" /* mftbu */
29e922b6
BS
585 "cmpw %0,%1\n\t"
586 "bne $-16"
587 : "=r" (retval), "=r" (junk));
588#endif
589 return retval;
590}
591
592#elif defined(__i386__)
593
594static inline int64_t cpu_get_real_ticks(void)
595{
596 int64_t val;
597 asm volatile ("rdtsc" : "=A" (val));
598 return val;
599}
600
601#elif defined(__x86_64__)
602
603static inline int64_t cpu_get_real_ticks(void)
604{
605 uint32_t low,high;
606 int64_t val;
607 asm volatile("rdtsc" : "=a" (low), "=d" (high));
608 val = high;
609 val <<= 32;
610 val |= low;
611 return val;
612}
613
614#elif defined(__hppa__)
615
616static inline int64_t cpu_get_real_ticks(void)
617{
618 int val;
619 asm volatile ("mfctl %%cr16, %0" : "=r"(val));
620 return val;
621}
622
623#elif defined(__ia64)
624
625static inline int64_t cpu_get_real_ticks(void)
626{
627 int64_t val;
628 asm volatile ("mov %0 = ar.itc" : "=r"(val) :: "memory");
629 return val;
630}
631
632#elif defined(__s390__)
633
634static inline int64_t cpu_get_real_ticks(void)
635{
636 int64_t val;
637 asm volatile("stck 0(%1)" : "=m" (val) : "a" (&val) : "cc");
638 return val;
639}
640
9b9c37c3 641#elif defined(__sparc__)
29e922b6
BS
642
643static inline int64_t cpu_get_real_ticks (void)
644{
645#if defined(_LP64)
646 uint64_t rval;
647 asm volatile("rd %%tick,%0" : "=r"(rval));
648 return rval;
649#else
9b9c37c3
RH
650 /* We need an %o or %g register for this. For recent enough gcc
651 there is an "h" constraint for that. Don't bother with that. */
29e922b6
BS
652 union {
653 uint64_t i64;
654 struct {
655 uint32_t high;
656 uint32_t low;
657 } i32;
658 } rval;
9b9c37c3
RH
659 asm volatile("rd %%tick,%%g1; srlx %%g1,32,%0; mov %%g1,%1"
660 : "=r"(rval.i32.high), "=r"(rval.i32.low) : : "g1");
29e922b6
BS
661 return rval.i64;
662#endif
663}
664
665#elif defined(__mips__) && \
666 ((defined(__mips_isa_rev) && __mips_isa_rev >= 2) || defined(__linux__))
667/*
668 * binutils wants to use rdhwr only on mips32r2
669 * but as linux kernel emulate it, it's fine
670 * to use it.
671 *
672 */
673#define MIPS_RDHWR(rd, value) { \
674 __asm__ __volatile__ (".set push\n\t" \
675 ".set mips32r2\n\t" \
676 "rdhwr %0, "rd"\n\t" \
677 ".set pop" \
678 : "=r" (value)); \
679 }
680
681static inline int64_t cpu_get_real_ticks(void)
682{
683 /* On kernels >= 2.6.25 rdhwr <reg>, $2 and $3 are emulated */
684 uint32_t count;
685 static uint32_t cyc_per_count = 0;
686
687 if (!cyc_per_count) {
688 MIPS_RDHWR("$3", cyc_per_count);
689 }
690
691 MIPS_RDHWR("$2", count);
692 return (int64_t)(count * cyc_per_count);
693}
694
14a6063a
RH
695#elif defined(__alpha__)
696
697static inline int64_t cpu_get_real_ticks(void)
698{
699 uint64_t cc;
700 uint32_t cur, ofs;
701
702 asm volatile("rpcc %0" : "=r"(cc));
703 cur = cc;
704 ofs = cc >> 32;
705 return cur - ofs;
706}
707
29e922b6
BS
708#else
709/* The host CPU doesn't have an easily accessible cycle counter.
710 Just return a monotonically increasing value. This will be
711 totally wrong, but hopefully better than nothing. */
712static inline int64_t cpu_get_real_ticks (void)
713{
714 static int64_t ticks = 0;
715 return ticks++;
716}
717#endif
718
2d8ebcf9
RH
719#ifdef CONFIG_PROFILER
720static inline int64_t profile_getclock(void)
721{
722 return cpu_get_real_ticks();
723}
724
725extern int64_t qemu_time, qemu_time_start;
726extern int64_t tlb_flush_time;
727extern int64_t dev_time;
728#endif
729
87ecb68b 730#endif