git: 5fad251f0563 - main - linuxkpi: Add `timer64_to_tm()`
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Mon, 07 Sep 2026 20:32:29 UTC
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 <dumbbell@FreeBSD.org>
AuthorDate: 2026-08-15 18:31:15 +0000
Commit: Jean-Sébastien Pédron <dumbbell@FreeBSD.org>
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