xref: /haiku/src/system/kernel/real_time_clock.cpp (revision 7749d0bb0c358a3279b1b9cc76d8376e900130a5)
1 /*
2  * Copyright 2004-2009, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3  * Copyright 2003, Jeff Ward, jeff@r2d2.stcloudstate.edu. All rights reserved.
4  *
5  * Distributed under the terms of the MIT License.
6  */
7 
8 
9 #include <KernelExport.h>
10 
11 #include <arch/real_time_clock.h>
12 #include <commpage.h>
13 #include <real_time_clock.h>
14 #include <real_time_data.h>
15 #include <syscalls.h>
16 #include <thread.h>
17 
18 #include <stdlib.h>
19 
20 //#define TRACE_TIME
21 #ifdef TRACE_TIME
22 #	define TRACE(x) dprintf x
23 #else
24 #	define TRACE(x)
25 #endif
26 
27 
28 #define RTC_SECONDS_DAY 86400
29 #define RTC_EPOCH_JULIAN_DAY 2440588
30 	// January 1st, 1970
31 
32 static struct real_time_data *sRealTimeData;
33 static bool sIsGMT = false;
34 static bigtime_t sTimezoneOffset = 0;
35 static char sTimezoneName[B_FILE_NAME_LENGTH] = "GMT";
36 
37 
38 static void
39 real_time_clock_changed()
40 {
41 	timer_real_time_clock_changed();
42 	user_timer_real_time_clock_changed();
43 }
44 
45 
46 /*! Write the system time to CMOS. */
47 static void
48 rtc_system_to_hw(void)
49 {
50 	uint32 seconds;
51 
52 	seconds = (arch_rtc_get_system_time_offset(sRealTimeData) + system_time()
53 		+ (sIsGMT ? 0 : sTimezoneOffset)) / 1000000;
54 
55 	arch_rtc_set_hw_time(seconds);
56 }
57 
58 
59 /*! Read the CMOS clock and update the system time accordingly. */
60 static void
61 rtc_hw_to_system(void)
62 {
63 	uint32 current_time;
64 
65 	current_time = arch_rtc_get_hw_time();
66 	set_real_time_clock(current_time + (sIsGMT ? 0 : sTimezoneOffset));
67 }
68 
69 
70 bigtime_t
71 rtc_boot_time(void)
72 {
73 	return arch_rtc_get_system_time_offset(sRealTimeData);
74 }
75 
76 
77 static int
78 rtc_debug(int argc, char **argv)
79 {
80 	if (argc < 2) {
81 		// If no arguments were given, output all useful data.
82 		uint32 currentTime;
83 		bigtime_t systemTimeOffset
84 			= arch_rtc_get_system_time_offset(sRealTimeData);
85 
86 		currentTime = (systemTimeOffset + system_time()) / 1000000;
87 		dprintf("system_time:  %Ld\n", system_time());
88 		dprintf("system_time_offset:    %Ld\n", systemTimeOffset);
89 		dprintf("current_time: %lu\n", currentTime);
90 	} else {
91 		// If there was an argument, reset the system and hw time.
92 		set_real_time_clock(strtoul(argv[1], NULL, 10));
93 	}
94 
95 	return 0;
96 }
97 
98 
99 status_t
100 rtc_init(kernel_args *args)
101 {
102 	sRealTimeData = (struct real_time_data*)allocate_commpage_entry(
103 		COMMPAGE_ENTRY_REAL_TIME_DATA, sizeof(struct real_time_data));
104 
105 	arch_rtc_init(args, sRealTimeData);
106 	rtc_hw_to_system();
107 
108 	add_debugger_command("rtc", &rtc_debug, "Set and test the real-time clock");
109 	return B_OK;
110 }
111 
112 
113 //	#pragma mark - public kernel API
114 
115 
116 void
117 set_real_time_clock_usecs(bigtime_t currentTime)
118 {
119 	arch_rtc_set_system_time_offset(sRealTimeData, currentTime - system_time());
120 	rtc_system_to_hw();
121 	real_time_clock_changed();
122 }
123 
124 
125 void
126 set_real_time_clock(uint32 currentTime)
127 {
128 	set_real_time_clock_usecs((bigtime_t)currentTime * 1000000);
129 }
130 
131 
132 uint32
133 real_time_clock(void)
134 {
135 	return (arch_rtc_get_system_time_offset(sRealTimeData) + system_time())
136 		/ 1000000;
137 }
138 
139 
140 bigtime_t
141 real_time_clock_usecs(void)
142 {
143 	return arch_rtc_get_system_time_offset(sRealTimeData) + system_time();
144 }
145 
146 
147 uint32
148 get_timezone_offset(void)
149 {
150 	return (time_t)(sTimezoneOffset / 1000000LL);
151 }
152 
153 
154 // #pragma mark -
155 
156 
157 /*!	Converts the \a tm data to seconds. Note that the base year is not
158 	1900 as in POSIX, but 1970.
159 */
160 uint32
161 rtc_tm_to_secs(const struct tm *tm)
162 {
163 	uint32 days;
164 	int year, month;
165 
166 	month = tm->tm_mon + 1;
167 	year = tm->tm_year + RTC_EPOCH_BASE_YEAR;
168 
169 	// Reference: Fliegel, H. F. and van Flandern, T. C. (1968).
170 	// Communications of the ACM, Vol. 11, No. 10 (October, 1968).
171 	days = tm->tm_mday - 32075 - RTC_EPOCH_JULIAN_DAY
172 		+ 1461 * (year + 4800 + (month - 14) / 12) / 4
173 		+ 367 * (month - 2 - 12 * ((month - 14) / 12)) / 12
174 		- 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4;
175 
176 	return days * RTC_SECONDS_DAY + tm->tm_hour * 3600 + tm->tm_min * 60
177 		+ tm->tm_sec;
178 }
179 
180 
181 void
182 rtc_secs_to_tm(uint32 seconds, struct tm *t)
183 {
184 	uint32 year, month, day, l, n;
185 
186 	// Reference: Fliegel, H. F. and van Flandern, T. C. (1968).
187 	// Communications of the ACM, Vol. 11, No. 10 (October, 1968).
188 	l = seconds / 86400 + 68569 + RTC_EPOCH_JULIAN_DAY;
189 	n = 4 * l / 146097;
190 	l = l - (146097 * n + 3) / 4;
191 	year = 4000 * (l + 1) / 1461001;
192 	l = l - 1461 * year / 4 + 31;
193 	month = 80 * l / 2447;
194 	day = l - 2447 * month / 80;
195 	l = month / 11;
196 	month = month + 2 - 12 * l;
197 	year = 100 * (n - 49) + year + l;
198 
199 	t->tm_mday = day;
200 	t->tm_mon = month - 1;
201 	t->tm_year = year - RTC_EPOCH_BASE_YEAR;
202 
203 	seconds = seconds % RTC_SECONDS_DAY;
204 	t->tm_hour = seconds / 3600;
205 
206 	seconds = seconds % 3600;
207 	t->tm_min = seconds / 60;
208 	t->tm_sec = seconds % 60;
209 }
210 
211 
212 //	#pragma mark - syscalls
213 
214 
215 bigtime_t
216 _user_system_time(void)
217 {
218 	syscall_64_bit_return_value();
219 
220 	return system_time();
221 }
222 
223 
224 status_t
225 _user_set_real_time_clock(bigtime_t time)
226 {
227 	if (geteuid() != 0)
228 		return B_NOT_ALLOWED;
229 
230 	set_real_time_clock_usecs(time);
231 	return B_OK;
232 }
233 
234 
235 status_t
236 _user_set_timezone(time_t timezoneOffset, const char *name, size_t nameLength)
237 {
238 	bigtime_t offset = (bigtime_t)timezoneOffset * 1000000LL;
239 
240 	if (geteuid() != 0)
241 		return B_NOT_ALLOWED;
242 
243 	TRACE(("old system_time_offset %Ld old %Ld new %Ld gmt %d\n",
244 		arch_rtc_get_system_time_offset(sRealTimeData), sTimezoneOffset,
245 		offset, sIsGMT));
246 
247 	if (name != NULL && nameLength > 0) {
248 		if (!IS_USER_ADDRESS(name)
249 			|| user_strlcpy(sTimezoneName, name, sizeof(sTimezoneName)) < 0)
250 			return B_BAD_ADDRESS;
251 	}
252 
253 	// We only need to update our time offset if the hardware clock
254 	// does not run in the local timezone.
255 	// Since this is shared data, we need to update it atomically.
256 	if (!sIsGMT) {
257 		arch_rtc_set_system_time_offset(sRealTimeData,
258 			arch_rtc_get_system_time_offset(sRealTimeData) + sTimezoneOffset
259 				- offset);
260 		real_time_clock_changed();
261 	}
262 
263 	sTimezoneOffset = offset;
264 
265 	TRACE(("new system_time_offset %Ld\n",
266 		arch_rtc_get_system_time_offset(sRealTimeData)));
267 
268 	return B_OK;
269 }
270 
271 
272 status_t
273 _user_get_timezone(time_t *_timezoneOffset, char *userName, size_t nameLength)
274 {
275 	time_t offset = (time_t)(sTimezoneOffset / 1000000LL);
276 
277 	if (_timezoneOffset != NULL
278 		&& (!IS_USER_ADDRESS(_timezoneOffset)
279 			|| user_memcpy(_timezoneOffset, &offset, sizeof(time_t)) < B_OK))
280 		return B_BAD_ADDRESS;
281 
282 	if (userName != NULL
283 		&& (!IS_USER_ADDRESS(userName)
284 			|| user_strlcpy(userName, sTimezoneName, nameLength) < 0))
285 		return B_BAD_ADDRESS;
286 
287 	return B_OK;
288 }
289 
290 
291 status_t
292 _user_set_real_time_clock_is_gmt(bool isGMT)
293 {
294 	// store previous value
295 	bool wasGMT = sIsGMT;
296 	if (geteuid() != 0)
297 		return B_NOT_ALLOWED;
298 
299 	sIsGMT = isGMT;
300 
301 	if (wasGMT != sIsGMT) {
302 		arch_rtc_set_system_time_offset(sRealTimeData,
303 			arch_rtc_get_system_time_offset(sRealTimeData)
304 				+ (sIsGMT ? 1 : -1) * sTimezoneOffset);
305 		real_time_clock_changed();
306 	}
307 
308 	return B_OK;
309 }
310 
311 
312 status_t
313 _user_get_real_time_clock_is_gmt(bool *_userIsGMT)
314 {
315 	if (_userIsGMT == NULL)
316 		return B_BAD_VALUE;
317 
318 	if (_userIsGMT != NULL
319 		&& (!IS_USER_ADDRESS(_userIsGMT)
320 			|| user_memcpy(_userIsGMT, &sIsGMT, sizeof(bool)) != B_OK))
321 		return B_BAD_ADDRESS;
322 
323 	return B_OK;
324 }
325 
326