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