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