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
35static struct real_time_data *sRealTimeData;
36#ifdef _COMPAT_MODE
37static struct real_time_data *sRealTimeDataCompat;
38#endif
39static bool sIsGMT = false;
40static bigtime_t sTimezoneOffset = 0;
41static char sTimezoneName[B_FILE_NAME_LENGTH] = "GMT";
42
43
44static void
45real_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. */
53static void
54rtc_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. */
66static void
67rtc_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
76bigtime_t
77rtc_boot_time(void)
78{
79	return arch_rtc_get_system_time_offset(sRealTimeData);
80}
81
82
83static int
84rtc_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
105status_t
106rtc_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
129void
130set_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
143void
144set_real_time_clock(unsigned long currentTime)
145{
146	set_real_time_clock_usecs((bigtime_t)currentTime * 1000000);
147}
148
149
150unsigned long
151real_time_clock(void)
152{
153	return (arch_rtc_get_system_time_offset(sRealTimeData) + system_time())
154		/ 1000000;
155}
156
157
158bigtime_t
159real_time_clock_usecs(void)
160{
161	return arch_rtc_get_system_time_offset(sRealTimeData) + system_time();
162}
163
164
165uint32
166get_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*/
178uint32
179rtc_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
199void
200rtc_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
233bigtime_t
234_user_system_time(void)
235{
236	syscall_64_bit_return_value();
237
238	return system_time();
239}
240
241
242status_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
253status_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
295status_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
314status_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
340status_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