The branch main has been updated by dumbbell: URL: https://cgit.FreeBSD.org/src/commit/?id=5fad251f05634e3220d36d51f624d77b41bc01c1
commit 5fad251f05634e3220d36d51f624d77b41bc01c1 Author: Jean-Sébastien Pédron <[email protected]> AuthorDate: 2026-08-15 18:31:15 +0000 Commit: Jean-Sébastien Pédron <[email protected]> CommitDate: 2026-09-07 20:31:39 +0000 linuxkpi: Add `timer64_to_tm()` The function converts a number of seconds since Epoch to a `struct tm`. The implementation was copied from `contrib/tzcode/localtime.c`. The amdgpu DRM driver started to use it in Linux 6.15. Reviewed by: bz Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D58758 --- sys/compat/linuxkpi/common/include/linux/time.h | 19 +++ sys/compat/linuxkpi/common/src/linux_timesub.c | 183 ++++++++++++++++++++++++ sys/conf/files | 2 + sys/modules/linuxkpi/Makefile | 1 + 4 files changed, 205 insertions(+) diff --git a/sys/compat/linuxkpi/common/include/linux/time.h b/sys/compat/linuxkpi/common/include/linux/time.h index ca77a20516ff..0bc47e4135c5 100644 --- a/sys/compat/linuxkpi/common/include/linux/time.h +++ b/sys/compat/linuxkpi/common/include/linux/time.h @@ -44,6 +44,21 @@ typedef int64_t time64_t; +/* Copied from `include/time.h` (userland). */ +struct tm { + int tm_sec; /* seconds after the minute [0-60] */ + int tm_min; /* minutes after the hour [0-59] */ + int tm_hour; /* hours since midnight [0-23] */ + int tm_mday; /* day of the month [1-31] */ + int tm_mon; /* months since January [0-11] */ + int tm_year; /* years since 1900 */ + int tm_wday; /* days since Sunday [0-6] */ + int tm_yday; /* days since January 1 [0-365] */ + int tm_isdst; /* Daylight Savings Time flag */ + long tm_gmtoff; /* offset from UTC in seconds */ + char *tm_zone; /* timezone abbreviation */ +}; + static inline struct timeval ns_to_timeval(const int64_t nsec) { @@ -138,4 +153,8 @@ get_seconds(void) return time_uptime; } +void linuxkpi_time64_to_tm(time64_t totalsecs, int offset, struct tm *result); +#define time64_to_tm(totalsecs, offset, result) \ + linuxkpi_time64_to_tm(totalsecs, offset, result) + #endif /* _LINUXKPI_LINUX_TIME_H_ */ diff --git a/sys/compat/linuxkpi/common/src/linux_timesub.c b/sys/compat/linuxkpi/common/src/linux_timesub.c new file mode 100644 index 000000000000..bc3fc0f1a702 --- /dev/null +++ b/sys/compat/linuxkpi/common/src/linux_timesub.c @@ -0,0 +1,183 @@ +/* + * This file is in the public domain, so clarified as of + * 1996-06-05 by Arthur David Olson. + */ + +/* + * The whole implementation of `timesub()` and its dependencies are copied from + * `contrib/tzcode/localtime.c` as of FreeBSD commit + * 28f617de7d9b9c708eacb3c2c13e5287e1b7354d. + */ + +#include <linux/time.h> + +enum { + SECSPERMIN = 60, + MINSPERHOUR = 60, + SECSPERHOUR = SECSPERMIN * MINSPERHOUR, + HOURSPERDAY = 24, + DAYSPERWEEK = 7, + DAYSPERNYEAR = 365, + DAYSPERLYEAR = DAYSPERNYEAR + 1, + MONSPERYEAR = 12, + YEARSPERREPEAT = 400 /* years before a Gregorian repeat */ +}; + +enum { + TM_SUNDAY, + TM_MONDAY, + TM_TUESDAY, + TM_WEDNESDAY, + TM_THURSDAY, + TM_FRIDAY, + TM_SATURDAY +}; + +enum { + TM_JANUARY, + TM_FEBRUARY, + TM_MARCH, + TM_APRIL, + TM_MAY, + TM_JUNE, + TM_JULY, + TM_AUGUST, + TM_SEPTEMBER, + TM_OCTOBER, + TM_NOVEMBER, + TM_DECEMBER +}; + +enum { + TM_YEAR_BASE = 1900, + TM_WDAY_BASE = TM_MONDAY, + EPOCH_YEAR = 1970, + EPOCH_WDAY = TM_THURSDAY +}; + +#define SECSPERDAY (SECSPERHOUR * HOURSPERDAY) +#define DAYSPERREPEAT (400 * 365 + 100 - 4 + 1) + +#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) + +#define TYPE_SIGNED(type) (((type) -1) < 0) + +static const int mon_lengths[2][MONSPERYEAR] = { + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } +}; + +static const int year_lengths[2] = { + DAYSPERNYEAR, DAYSPERLYEAR +}; + +static time_t +leaps_thru_end_of_nonneg(time_t y) +{ + return y / 4 - y / 100 + y / 400; +} + +static time_t +leaps_thru_end_of(time_t y) +{ + return (y < 0 + ? -1 - leaps_thru_end_of_nonneg(-1 - y) + : leaps_thru_end_of_nonneg(y)); +} + +static struct tm * +timesub(const time64_t *timep, int offset, struct tm *tmp) +{ + time_t tdays; + const int * ip; + int corr; + int idays, rem, dayoff, dayrem; + time_t y; + + /* If less than SECSPERMIN, the number of seconds since the + most recent positive leap second; otherwise, do not add 1 + to localtime tm_sec because of leap seconds. */ + time_t secs_since_posleap = SECSPERMIN; + + corr = 0; + + /* Calculate the year, avoiding integer overflow even if + time_t is unsigned. */ + tdays = *timep / SECSPERDAY; + rem = *timep % SECSPERDAY; + rem += offset % SECSPERDAY - corr % SECSPERDAY + 3 * SECSPERDAY; + dayoff = offset / SECSPERDAY - corr / SECSPERDAY + rem / SECSPERDAY - 3; + rem %= SECSPERDAY; + /* y = (EPOCH_YEAR + + floor((tdays + dayoff) / DAYSPERREPEAT) * YEARSPERREPEAT), + sans overflow. But calculate against 1570 (EPOCH_YEAR - + YEARSPERREPEAT) instead of against 1970 so that things work + for localtime values before 1970 when time_t is unsigned. */ + dayrem = tdays % DAYSPERREPEAT; + dayrem += dayoff % DAYSPERREPEAT; + y = (EPOCH_YEAR - YEARSPERREPEAT + + ((1 + dayoff / DAYSPERREPEAT + dayrem / DAYSPERREPEAT + - ((dayrem % DAYSPERREPEAT) < 0) + + tdays / DAYSPERREPEAT) + * YEARSPERREPEAT)); + /* idays = (tdays + dayoff) mod DAYSPERREPEAT, sans overflow. */ + idays = tdays % DAYSPERREPEAT; + idays += dayoff % DAYSPERREPEAT + 2 * DAYSPERREPEAT; + idays %= DAYSPERREPEAT; + /* Increase Y and decrease IDAYS until IDAYS is in range for Y. */ + while (year_lengths[isleap(y)] <= idays) { + int tdelta = idays / DAYSPERLYEAR; + int_fast32_t ydelta = tdelta + !tdelta; + time_t newy = y + ydelta; + register int leapdays; + leapdays = leaps_thru_end_of(newy - 1) - + leaps_thru_end_of(y - 1); + idays -= ydelta * DAYSPERNYEAR; + idays -= leapdays; + y = newy; + } + + if (!TYPE_SIGNED(time_t) && y < TM_YEAR_BASE) { + int signed_y = y; + tmp->tm_year = signed_y - TM_YEAR_BASE; + } else if ((!TYPE_SIGNED(time_t) || INT_MIN + TM_YEAR_BASE <= y) + && y - TM_YEAR_BASE <= INT_MAX) + tmp->tm_year = y - TM_YEAR_BASE; + else { + return NULL; + } + tmp->tm_yday = idays; + /* + ** The "extra" mods below avoid overflow problems. + */ + tmp->tm_wday = (TM_WDAY_BASE + + ((tmp->tm_year % DAYSPERWEEK) + * (DAYSPERNYEAR % DAYSPERWEEK)) + + leaps_thru_end_of(y - 1) + - leaps_thru_end_of(TM_YEAR_BASE - 1) + + idays); + tmp->tm_wday %= DAYSPERWEEK; + if (tmp->tm_wday < 0) + tmp->tm_wday += DAYSPERWEEK; + tmp->tm_hour = rem / SECSPERHOUR; + rem %= SECSPERHOUR; + tmp->tm_min = rem / SECSPERMIN; + tmp->tm_sec = rem % SECSPERMIN; + + /* Use "... ??:??:60" at the end of the localtime minute containing + the second just before the positive leap second. */ + tmp->tm_sec += secs_since_posleap <= tmp->tm_sec; + + ip = mon_lengths[isleap(y)]; + for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon)) + idays -= ip[tmp->tm_mon]; + tmp->tm_mday = idays + 1; + tmp->tm_isdst = 0; + return tmp; +} + +void +linuxkpi_time64_to_tm(time64_t totalsecs, int offset, struct tm *result) +{ + timesub(&totalsecs, offset, result); +} diff --git a/sys/conf/files b/sys/conf/files index e6f996812a8f..8dd5a6a6873b 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -4772,6 +4772,8 @@ compat/linuxkpi/common/src/linux_skbuff.c optional compat_linuxkpi \ compile-with "${LINUXKPI_C}" compat/linuxkpi/common/src/linux_slab.c optional compat_linuxkpi \ compile-with "${LINUXKPI_C}" +compat/linuxkpi/common/src/linux_timesub.c optional compat_linuxkpi \ + compile-with "${LINUXKPI_C}" compat/linuxkpi/common/src/linux_usb.c optional compat_linuxkpi usb \ compile-with "${LINUXKPI_C}" compat/linuxkpi/common/src/linux_work.c optional compat_linuxkpi \ diff --git a/sys/modules/linuxkpi/Makefile b/sys/modules/linuxkpi/Makefile index da8d003e1d22..35dc19f93185 100644 --- a/sys/modules/linuxkpi/Makefile +++ b/sys/modules/linuxkpi/Makefile @@ -35,6 +35,7 @@ SRCS= linux_compat.c \ linux_skbuff.c \ linux_slab.c \ linux_tasklet.c \ + linux_timesub.c \ linux_usb.c \ linux_work.c \ linux_xarray.c
