• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2  * Use of this source code is governed by a BSD-style license that can be
3  * found in the LICENSE file.
4  */
5 
6 #include "cras_types.h"
7 #include "cras_util.h"
8 #include "utlist.h"
9 
10 #include <time.h>
11 
12 /* Represents an armed timer.
13  * Members:
14  *    ts - timespec at which the timer should fire.
15  *    cb - Callback to call when the timer expires.
16  *    cb_data - Data passed to the callback.
17  */
18 struct cras_timer {
19 	struct timespec ts;
20 	void (*cb)(struct cras_timer *t, void *data);
21 	void *cb_data;
22 	struct cras_timer *next, *prev;
23 };
24 
25 /* Timer Manager, keeps a list of active timers. */
26 struct cras_tm {
27 	struct cras_timer *timers;
28 };
29 
30 /* Local Functions. */
31 
32 /* Adds ms milliseconds to ts. */
add_ms_ts(struct timespec * ts,unsigned int ms)33 static inline void add_ms_ts(struct timespec *ts, unsigned int ms)
34 {
35 	if (ms >= 1000) {
36 		ts->tv_sec += ms / 1000;
37 		ms %= 1000;
38 	}
39 	ts->tv_nsec += ms * 1000000L;
40 	if (ts->tv_nsec >= 1000000000L) {
41 		ts->tv_sec += ts->tv_nsec / 1000000000L;
42 		ts->tv_nsec %= 1000000000L;
43 	}
44 }
45 
46 /* Checks if timespec a is less than b. */
timespec_sooner(const struct timespec * a,const struct timespec * b)47 static inline int timespec_sooner(const struct timespec *a,
48 				  const struct timespec *b)
49 {
50 	return (a->tv_sec < b->tv_sec ||
51 		(a->tv_sec == b->tv_sec && a->tv_nsec <= b->tv_nsec));
52 }
53 
54 /* Exported Interface. */
55 
cras_tm_create_timer(struct cras_tm * tm,unsigned int ms,void (* cb)(struct cras_timer * t,void * data),void * cb_data)56 struct cras_timer *cras_tm_create_timer(
57 		struct cras_tm *tm,
58 		unsigned int ms,
59 		void (*cb)(struct cras_timer *t, void *data),
60 		void *cb_data)
61 {
62 	struct cras_timer *t;
63 
64 	t = calloc(1, sizeof(*t));
65 	if (!t)
66 		return NULL;
67 
68 	t->cb = cb;
69 	t->cb_data = cb_data;
70 
71 	clock_gettime(CLOCK_MONOTONIC_RAW, &t->ts);
72 	add_ms_ts(&t->ts, ms);
73 
74 	DL_APPEND(tm->timers, t);
75 
76 	return t;
77 }
78 
cras_tm_cancel_timer(struct cras_tm * tm,struct cras_timer * t)79 void cras_tm_cancel_timer(struct cras_tm *tm, struct cras_timer *t)
80 {
81 	DL_DELETE(tm->timers, t);
82 	free(t);
83 }
84 
cras_tm_init()85 struct cras_tm *cras_tm_init()
86 {
87 	return calloc(1, sizeof(struct cras_tm));
88 }
89 
cras_tm_deinit(struct cras_tm * tm)90 void cras_tm_deinit(struct cras_tm *tm)
91 {
92 	struct cras_timer *t;
93 
94 	DL_FOREACH(tm->timers, t) {
95 		DL_DELETE(tm->timers, t);
96 		free(t);
97 	}
98 	free(tm);
99 }
100 
cras_tm_get_next_timeout(const struct cras_tm * tm,struct timespec * ts)101 int cras_tm_get_next_timeout(const struct cras_tm *tm, struct timespec *ts)
102 {
103 	struct cras_timer *t;
104 	struct timespec now;
105 	struct timespec *min;
106 
107 	if (!tm->timers)
108 		return 0;
109 
110 	min = &tm->timers->ts;
111 	DL_FOREACH(tm->timers, t)
112 		if (timespec_sooner(&t->ts, min))
113 			min = &t->ts;
114 
115 	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
116 
117 	if (timespec_sooner(min, &now)) {
118 		/* Timer already expired. */
119 		ts->tv_sec = ts->tv_nsec = 0;
120 		return 1;
121 	}
122 
123 	subtract_timespecs(min, &now, ts);
124 	return 1;
125 }
126 
cras_tm_call_callbacks(struct cras_tm * tm)127 void cras_tm_call_callbacks(struct cras_tm *tm)
128 {
129 	struct timespec now;
130 	struct cras_timer *t, *next;
131 
132 	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
133 
134 	/* Don't use DL_FOREACH to iterate timers because in each loop the
135 	 * next timer pointer is stored for later access but it could be
136 	 * cancelled and freed in current timer's callback causing invalid
137 	 * memory access. */
138 	t = tm->timers;
139 	while (t) {
140 		next = t->next;
141 		if (timespec_sooner(&t->ts, &now)) {
142 			t->cb(t, t->cb_data);
143 			/* Update next timer because it could have been modified
144 			 * in t->cb(). */
145 			next = t->next;
146 			cras_tm_cancel_timer(tm, t);
147 		}
148 		t = next;
149 	}
150 }
151