diff options
Diffstat (limited to 'libgloss/m68k/atari/atari-gettod.c')
-rw-r--r-- | libgloss/m68k/atari/atari-gettod.c | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/libgloss/m68k/atari/atari-gettod.c b/libgloss/m68k/atari/atari-gettod.c new file mode 100644 index 0000000..5477916 --- /dev/null +++ b/libgloss/m68k/atari/atari-gettod.c @@ -0,0 +1,135 @@ +/* + Copyright (C) 2025 Mikael Hildenborg + SPDX-License-Identifier: BSD-2-Clause +*/ + +#include <sys/time.h> +#include <time.h> +#include "atari-gem_errno.h" +#include "atari-traps.h" + +/* + Atari can only handle dates from 1980 to 2107. + time_t on m68k is 32bit signed, so that gives us an upper limit of 2038. + The code below uses that limitation to simplify the code. +*/ + +#define SEC_1900_TO_1980 2524521600 +#define SEC_1900_TO_MAX 0x7fffffff +#define SECONDS_IN_A_DAY (24 * 60 * 60) +#define SEC_JAN_AND_FEB ((31 + 29) * SECONDS_IN_A_DAY) // In a leap year +#define SECONDS_IN_A_YEAR (365 * SECONDS_IN_A_DAY) + +static const short month_to_day[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; + +int gettimeofday(struct timeval* tv, void* __tz) +{ + struct timezone* tz = __tz; + if (tz != 0) + { + // Support for timezone have been removed from linux glibc, so we just fill in a zero timezone. + tz->tz_minuteswest = 0; + tz->tz_dsttime = 0; + } + if (tv != 0) + { + unsigned short date = trap1_2a(); + unsigned short time = trap1_2c(); + + int year = ((date >> 9) & 0x7f); // from 1980 + int month = ((date >> 5) & 0xf) - 1; + int day = date & 0x1f; + int hour = ((time >> 11) & 0x1f); + int min = ((time >> 5) & 0x3f); + int sec = (time & 0x1f) * 2; + + // Days passed in current year + time_t days = day + month_to_day[month]; + // Add days for every passed year + days += year * 365; + + // Add days for every passed leap year + int months = year * 12 + month; // total months + months -= 2; // remove januari and februari + if (months > 0) + { + // We must have passed at least one leap day. + int leap_days = (months / (12*4)) + 1; + days += leap_days; + } + + // Add it all together + tv->tv_sec = (((((days * 24) + hour) * 60) + min) * 60) + sec + SEC_1900_TO_1980; + tv->tv_usec = 0; + } + return 0; +} + +int settimeofday(const struct timeval* tv, const struct timezone* tz) +{ + // Support for timezone have been removed from linux glibc, so we just ignore it. + if (tv != 0) + { + if (tv->tv_sec < SEC_1900_TO_1980 || tv->tv_sec >= SEC_1900_TO_MAX) + { + // Outside the ranges we can handle. + gem_error_to_errno(GEM_EBADRQ); + return -1; + } + + time_t seconds = tv->tv_sec - SEC_1900_TO_1980; + int year = 0; + time_t ysec = SECONDS_IN_A_YEAR; + do + { + ysec = SECONDS_IN_A_YEAR; + if ((year % 4) == 0) + { + ysec += SECONDS_IN_A_DAY; + } + ++year; + seconds -= ysec; + } while (seconds >= 0); + --year; + seconds += ysec; + + int minutes = seconds / 60; + int hours = minutes / 60; + int days = hours / 24; + int month = 0; + int leap = year % 4; + short ld = month_to_day[0]; + for (int m = 0; m < 12; ++m) + { + short d = month_to_day[m + 1]; + if (leap == 0 && m > 0) {d += 1;} + if (d > days) + { + month = m; + days -= ld; + break; + } + ld = d; + } + + unsigned short date = (unsigned short)(year << 9); + date |= (unsigned short)((month + 1) << 5); + date |= (unsigned short)days; + unsigned short time = (unsigned short)((hours % 24) << 11); + time |= (unsigned short)((minutes % 60) << 5); + time |= (unsigned short)((seconds % 60) >> 1); + + int err; + if ((err = trap1_2b(date)) < 0) + { + gem_error_to_errno(err); + return -1; + } + if ((err = trap1_2d(time)) < 0) + { + gem_error_to_errno(err); + return -1; + } + } + return 0; +} |