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 %lld old %lld new %lld 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 %lld\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