@@ -10,6 +10,16 @@ config RTC_MC146818_LIB
bool
select RTC_LIB
+config RTC_LIB_KUNIT_TEST
+ tristate "KUnit test for RTC lib functions" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ select RTC_LIB
+ help
+ Enable this option to test RTC library functions.
+
+ If unsure, say N.
+
menuconfig RTC_CLASS
bool "Real Time Clock"
default n
@@ -178,3 +178,4 @@ obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o
obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o
obj-$(CONFIG_RTC_DRV_XGENE) += rtc-xgene.o
obj-$(CONFIG_RTC_DRV_ZYNQMP) += rtc-zynqmp.o
+obj-$(CONFIG_RTC_LIB_KUNIT_TEST) += lib_test.o
@@ -6,6 +6,8 @@
* Author: Alessandro Zummo <a.zummo@towertech.it>
*
* based on arch/arm/common/rtctime.c and other bits
+ *
+ * Author: Cassio Neri <cassio.neri@gmail.com> (rtc_time64_to_tm)
*/
#include <linux/export.h>
@@ -22,8 +24,6 @@ static const unsigned short rtc_ydays[2][13] = {
{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
};
-#define LEAPS_THRU_END_OF(y) ((y) / 4 - (y) / 100 + (y) / 400)
-
/*
* The number of days in the month.
*/
@@ -43,41 +43,68 @@ int rtc_year_days(unsigned int day, unsigned int month, unsigned int year)
EXPORT_SYMBOL(rtc_year_days);
/*
- * rtc_time64_to_tm - Converts time64_t to rtc_time.
- * Convert seconds since 01-01-1970 00:00:00 to Gregorian date.
+ * This function converts time64_t to rtc_time.
+ *
+ * @param[in] time The number of seconds since 01-01-1970 00:00:00.
+ * (Must be positive.)
+ * @param[out] tm Pointer to the struct rtc_time.
*/
void rtc_time64_to_tm(time64_t time, struct rtc_time *tm)
{
- unsigned int month, year, secs;
+ unsigned int secs;
int days;
+ u32 r0, n1, q1;
+ u32 r1, n2, q2, r2;
+ u64 u2;
+ u32 n3, q3, r3;
+
+ u32 j;
+ u32 y;
+ u32 m, d;
+
/* time must be positive */
days = div_s64_rem(time, 86400, &secs);
/* day of the week, 1970-01-01 was a Thursday */
tm->tm_wday = (days + 4) % 7;
- year = 1970 + days / 365;
- days -= (year - 1970) * 365
- + LEAPS_THRU_END_OF(year - 1)
- - LEAPS_THRU_END_OF(1970 - 1);
- while (days < 0) {
- year -= 1;
- days += 365 + is_leap_year(year);
- }
- tm->tm_year = year - 1900;
- tm->tm_yday = days + 1;
-
- for (month = 0; month < 11; month++) {
- int newdays;
-
- newdays = days - rtc_month_days(month, year);
- if (newdays < 0)
- break;
- days = newdays;
- }
- tm->tm_mon = month;
- tm->tm_mday = days + 1;
+ /*
+ * The following algorithm is Proposition 6.3 of Neri and Schneider,
+ * "Euclidean Affine Functions and Applications to Calendar Algorithms".
+ * https://arxiv.org/abs/2102.06959
+ */
+
+ r0 = days + 719468;
+
+ n1 = 4 * r0 + 3;
+ q1 = n1 / 146097;
+ r1 = n1 % 146097 / 4;
+
+ n2 = 4 * r1 + 3;
+ u2 = ((u64) 2939745) * n2;
+ q2 = u2 >> 32;
+ r2 = ((u32) u2) / 2939745 / 4;
+
+ n3 = 2141 * r2 + 197913;
+ q3 = n3 >> 16;
+ r3 = ((u16) n3) / 2141;
+
+ j = r2 >= 306;
+ y = 100 * q1 + q2 + j;
+ m = j ? q3 - 12 : q3;
+ d = r3 + 1;
+
+ tm->tm_year = y - 1900;
+ tm->tm_mon = m - 1;
+ tm->tm_mday = d;
+
+ /*
+ * r2 contains the number of days since previous Mar 1st and j == true
+ * if and only if month is Jan or Feb. The bellow is then a correction
+ * to get the numbers of days since previous Jan 1st.
+ */
+ tm->tm_yday = j ? r2 - 305 : r2 + 60 + is_leap_year(y);
tm->tm_hour = secs / 3600;
secs -= tm->tm_hour * 3600;
new file mode 100644
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: LGPL-2.1+
+
+#include <kunit/test.h>
+#include <linux/rtc.h>
+
+/*
+ * Advance a date by one day.
+ */
+static void advance_date(int *year, int *month, int *mday, int *yday)
+{
+ if (*mday != rtc_month_days(*month - 1, *year)) {
+ ++*mday;
+ ++*yday;
+ return;
+ }
+
+ *mday = 1;
+ if (*month != 12) {
+ ++*month;
+ ++*yday;
+ return;
+ }
+
+ *month = 1;
+ *yday = 1;
+ ++*year;
+}
+
+/*
+ * Checks every day in a 160000 years interval starting on 1970-01-01
+ * against the expected result.
+ */
+static void rtc_time64_to_tm_test_date_range(struct kunit *test)
+{
+ /*
+ * 160000 years = (160000 / 400) * 400 years
+ * = (160000 / 400) * 146097 days
+ * = (160000 / 400) * 146097 * 86400 seconds
+ */
+ time64_t total_secs = ((time64_t) 160000) / 400 * 146097 * 86400;
+ int year = 1970;
+ int month = 1;
+ int mday = 1;
+ int yday = 1;
+
+ struct rtc_time result;
+ time64_t secs;
+
+ for (secs = 0; secs <= total_secs; secs += 86400) {
+
+ rtc_time64_to_tm(secs, &result);
+
+ #define FAIL_MSG "%d/%02d/%02d (%2d) : %ld", \
+ year, month, mday, yday, secs/86400
+
+ KUNIT_ASSERT_EQ_MSG(test, year - 1900, result.tm_year, FAIL_MSG);
+ KUNIT_ASSERT_EQ_MSG(test, month - 1, result.tm_mon, FAIL_MSG);
+ KUNIT_ASSERT_EQ_MSG(test, mday, result.tm_mday, FAIL_MSG);
+ KUNIT_ASSERT_EQ_MSG(test, yday, result.tm_yday, FAIL_MSG);
+
+ advance_date(&year, &month, &mday, &yday);
+ }
+}
+
+static struct kunit_case rtc_lib_test_cases[] = {
+ KUNIT_CASE(rtc_time64_to_tm_test_date_range),
+ {}
+};
+
+static struct kunit_suite rtc_lib_test_suite = {
+ .name = "rtc_lib_test_cases",
+ .test_cases = rtc_lib_test_cases,
+};
+
+kunit_test_suite(rtc_lib_test_suite);
@@ -220,7 +220,11 @@ void rtc_timer_do_work(struct work_struct *work);
static inline bool is_leap_year(unsigned int year)
{
- return (!(year % 4) && (year % 100)) || !(year % 400);
+ /* This implementation is more branch-predictor friendly than the
+ * traditional:
+ * return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
+ */
+ return year % 100 != 0 ? year % 4 == 0 : year % 400 == 0;
}
#define devm_rtc_register_device(device) \