git: 5fad251f0563 - main - linuxkpi: Add `timer64_to_tm()`

From: Jean-Sébastien Pédron <dumbbell_at_FreeBSD.org>
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