diff mbox

[RFC,13/14] Documentation/RTC: add document of ACPI TAD and EFI TIME driver

Message ID 1387439515-8926-14-git-send-email-jlee@suse.com (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Chun-Yi Lee Dec. 19, 2013, 7:51 a.m. UTC
This patch add rtc-tz.txt document to explain the RTC driver of
ACPI TAD, EFI TIME. It focus on the timezone field and CMOS RTC Not
Present bit of ACPI 5.0.

Signed-off-by: Lee, Chun-Yi <jlee@suse.com>
---
 Documentation/rtc-tz.txt |  510 ++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 510 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/rtc-tz.txt
diff mbox

Patch

diff --git a/Documentation/rtc-tz.txt b/Documentation/rtc-tz.txt
new file mode 100644
index 0000000..7dfe523
--- /dev/null
+++ b/Documentation/rtc-tz.txt
@@ -0,0 +1,510 @@ 
+
+	ACPI TAD and EFI TIME Drivers for Timezone and Daylight
+	=======================================================
+
+In ACPI 5.0 ACPI TAD (Time and Alram Device) and EFI, they defined
+Timezone and Daylight field in time struct:
+
+    *	ACPI TAD ... INT16 TimeZone
+		         -1440 to 1440 or 2047 (unspecified)
+		         Time zone field is the number of minutes that
+		         the local time lags behind the UTC time.
+		         Follow this equation: Localtime = UTC - TimeZone
+		     UINT8 Daylight
+			Daylight is a bitmask containing the daylight
+			savings time information for the time:
+			Bit0: 1 = the time is affected by daylight savings time,
+			      0= time is not affected by daylight savings.
+			Bit1: 1= the time has been adjusted for daylight savings time,
+			      0= the time hasn't been adjusted for daylight savings.
+
+    *	EFI TIME ... INT16 TimeZone
+			-1440 to 1440 or 2047 (unspecified)
+			Time zone field is the number of minutes that
+			the local time lags behind the UTC time.
+			Follow this equation: Localtime = UTC - TimeZone
+		     UINT8 Daylight
+			EFI_TIME_ADJUST_DAYLIGHT 0x01
+			    indicates if the time is affected by daylight
+			    savings time or not.
+			EFI_TIME_IN_DAYLIGHT 0x02
+			    is set, the time has been adjusted for daylight
+			    savings time.
+
+Then, the Timzone and daylight definition in GNU struct tm:
+
+    *	GNU struct tm ... long int __tm_gmtoff;	/* Seconds east of UTC.	*/
+			  int tm_isdst;		/* DST.         [-1/0/1]*/
+
+For Timzone, the definition match between ACPI TAD and EFI TIME. But it
+different with GNU time struct. GNU's tm_gmtoff is "Seconds east of UTC".
+Simply say, the timzone of ACPI/EFI and GNU are sign difference.
+
+Example 1:
+  'Asia/Taipei'	UTC +8
+	GNU:		tm_gmtoff = 28800 seconds.
+			Taiwan is east of UTC, so it's positive number.
+	ACPI or EFI:	TimeZone = -480 hours
+			Timezone = UTC - Localtime = UTC - Taipei time
+			So, it's negative number.
+Example 2:
+  'Americ/Los_Angeles'	UTC -8
+	GNU:		tm_gmtoff = -28800 seconds.
+			Los Angeles is west of UTC, so it's negative number.
+	ACPI or EFI:	TimeZone = 480 hours
+			Timezone = UTC - Localtime = UTC - Taipei time
+			So, it's positive number.
+
+For Daylight, due to GNU's tm_isdst is only define: '1' means current
+time is in daylight savings. '0' means not. '-1' means non-available.
+So, 1b is the only value from ACPI/EFI will transfer to '1' in GNU.
+When ACPI or EFI value is:
+
+    * 11b: Time is affected by daylight and has been adjusted for daylight
+	   tm->tm_isdst = 1
+    * 01b: Time is affected by daylight but hasn't been adjusted for daylight
+	   tm->tm_isdst = 0
+    * 00b(or 10b): Time is affected by daylight
+	   	   tm->tm_isdst = -1
+
+The above data tranfer work of Timezone and Daylight will handled by RTC driver
+to ACPI TAD and EFI TIME: rtc-acpitad and rtc-efi.
+
+SYSFS INTERFACE
+---------------
+
+The sysfs interface under /sys/class/rtc/rtcN provides access to various
+rtc attributes without requiring the use of ioctls. Here only have one new
+sysfs interface for grab the capabilities:
+
+caps:		This interface will return a bitmap of capabilitites to the RTC
+		interface. Currently it indicates the RTC has capability for
+		handle Timezone or Daylight:
+			Bit0: Timezone. Set this bit means this interface has
+			      capability to store Timezone
+			Bit1: Daylight: Set this bit means this interface has
+			      capability to store Daylight savings time.
+		There already have the following definition in linux/rtc.h:
+			#define        RTC_TZ_CAP      (1 << 0)
+			#define        RTC_DST_CAP     (1 << 1)
+
+IOCTL INTERFACE
+---------------
+
+Here create 3 new ioctl functions for read/set timezone and read the
+capabilities of RTC interface.
+
+    *	RTC_RD_GMTOFF, RTC_SET_GMTOFF ... Used to read and set timezone value.
+	Due to support the GNU tm_gmtoff format, so the input/output value
+	is "Seconds east of UTC". RTC drivers, rtc-acpitad and rtc-efi, done
+	the data transfer work.
+	The rtc-acpitad and rtc-efi driver also avoid to overwrite the timezone
+	field in their time struct in RTC_SET_TIME.
+
+    *	RTC_CAPS_READ ... As the caps sysfs interface, this ioctl provides
+	interface to userspace application for grab capabilities of current
+	RTC interface. Application can early check does this interface support
+	Timezone or Daylight.
+
+CMOS RTC Not Present flag
+-------------------------
+
+ACPI 5.0 spec defines a "CMOS RTC Not Present" flag in IA-PC Boot
+Architecture Flags of FADT. If this bit set, that means OSPM need uses the
+ACPI Time and Alarm device instead. Software should not access RTC through
+CMOS interface.
+
+In Linux kernel, the defaul wallclock functions deal with RTC by CMOS, it
+should move to ACPI TAD or EFI Time servcies. ACPI Time and Alarm device is
+described in DSDT that need wait until DSDT parsed by kernel in subsystem
+initial stage, so it can not used to deal with wallclock when system boot.
+
+Current solution of "CMOS RTC Not Present" is switch to EFI time services.
+On x86_64 EFI machine kernel will deal with RTC by EFI time services. In
+initial process, system time will adjusted base on timezone value from EFI
+time services, it also set persistent_clock_is_local global variable to
+avoid user space adjust system time by userland timezone again.
+
+The above efi warp clock mechanism will triggered on x86_64 EFI machine when
+timezone value is neither 0 nor 2047(UNSPECIFIED), kernel assume the value
+of RTC is local time. On the other hand, system just follow the old logic
+when timezone value from EFI is 0 or 2047, kernel assume the value of RTC is
+UTC time.
+
+About the 2047(EFI_UNSPECIFIED_TIMEZONE) value, it's the default value of
+UEFI BIOS if there didn't have any software set it through EFI interface.
+We can _NOT_ follow EFI spec to interpret the RTC time as a local time if
+timezone value is EFI_UNSPECIFIED_TIMEZONE, that's because Linux stored
+UTC to BIOS on shipped UEFI machines.
+
+
+-------------------- 8< ---------------- 8< -----------------------------
+
+/*
+ * Timezone of ACPI/EFI RTC Driver Test Program
+ *
+ * Compile with:
+ *      gcc rtc-tz-test.c -o rtc-tz-test
+ *
+ * Copyright (C) 2013 SUSE Linux Products GmbH. All rights reserved.
+ * Written by Lee, Chun-Yi (jlee@suse.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/rtc.h>
+
+#ifndef RTC_RD_GMTOFF
+#define RTC_RD_GMTOFF   _IOR('p', 0x13, long int)    /* Read time zone return seconds east of UTC */
+#define RTC_SET_GMTOFF  _IOW('p', 0x14, long int)    /* Set time zone input seconds east of UTC */
+#endif
+#ifndef RTC_CAPS_READ
+#define RTC_CAPS_READ   _IOR('p', 0x15, unsigned int)    /* Get capabilities, e.g. TZ, DST */
+#endif
+/* Time Zone and Daylight capabilities */
+#ifndef RTC_TZ_CAP
+#define RTC_TZ_CAP      (1 << 0)
+#define RTC_DST_CAP     (1 << 1)
+#endif
+
+#define MAX_DEV         5
+#define ADJUST_MIN      60
+#define DEFAULT_TZ      28800           /* GMT offset of Taiwan R.O.C (Seconds east of UTC)*/
+#define ADJUST_TZ       -28800          /* GMT offset of Los Angeles (Seconds east of UTC) */
+
+static const char dev_path[] = "/dev/rtc";
+static const char sys_path[] = "/sys/class/rtc/rtc";
+
+static const struct {
+        const char *driver_name;
+        const char *name;
+} names[] = {
+        {"rtc_cmos", "CMOS"},
+        {"rtc-efi", "EFI"},
+        {"rtc-acpitad", "ACPI-TAD"},
+};
+
+struct rtc_dev {
+        char dev_path[10];
+        char sys_path[21];
+        char name[15];
+        char driver_name[15];
+        unsigned int caps;
+};
+
+struct rtc_dev rtc_devs[5];
+
+void search_rtc_dev(void)
+{
+        int i, j, fd, ret;
+        FILE *fin;
+
+        for (i = 0; i <= MAX_DEV; i++)
+        {
+                char path_tmp[30];
+                sprintf(path_tmp, "%s%d", dev_path, i);
+
+                fd = open(path_tmp, O_RDONLY);
+                if (fd !=  -1) {
+                        struct rtc_dev *dev = &rtc_devs[i];
+
+                        memcpy(dev->dev_path, path_tmp, 10);
+                        sprintf(dev->sys_path, "%s%d/", sys_path, i);
+
+                        sprintf(path_tmp, "%s%s", dev->sys_path, "name");
+                        if ((fin = fopen(path_tmp, "r")) != NULL)
+                                fscanf(fin, "%s", dev->driver_name);
+                        fclose(fin);
+
+                        for (j = 0; j < sizeof(names)/sizeof(names[0]); j++) {
+                                if (!strcmp(dev->driver_name, names[j].driver_name))
+                                        memcpy(dev->name, names[j].name, strlen(names[j].name));
+                        }
+
+                        ioctl(fd, RTC_CAPS_READ, &dev->caps);
+                        close(fd);
+                }
+        }
+}
+
+void print_rtc_dev(struct rtc_dev *dev)
+{
+        long int gmtoff;
+        struct rtc_time rtc_tm;
+        int fd, ret;
+
+        fd = open(dev->dev_path, O_RDONLY);
+
+        printf("Name: %s(%s)\n", dev->name, dev->driver_name);
+        printf("    Device Path: %s\n", dev->dev_path);
+        printf("    Sysfs Path : %s\n", dev->sys_path);
+
+        /* Read the RTC time/date */
+        ret = ioctl(fd, RTC_RD_TIME, &rtc_tm);
+        if (ret == -1)
+                perror("RTC_RD_TIME ioctl");
+
+        printf("    RTC date/time: %d-%d-%d %02d:%02d:%02d\n",
+                rtc_tm.tm_mday, rtc_tm.tm_mon + 1, rtc_tm.tm_year + 1900,
+                rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec);
+        printf("    Is Daylight: %d (%s)\n", rtc_tm.tm_isdst,
+                (rtc_tm.tm_isdst)? (rtc_tm.tm_isdst < 0)? "NOT AVAILABLE":"INEFFECT":"NOT IN EFFECT");
+
+        printf("    Capabilities: %d (%s %s)\n", dev->caps,
+                (dev->caps & RTC_TZ_CAP)? "TZ":"",
+                (dev->caps & RTC_DST_CAP)? "DST":"");
+
+        /* Read the GMTOFF (Seconds east of UTC) */
+        ret = ioctl(fd, RTC_RD_GMTOFF, &gmtoff);
+        if (ret == -1)
+                printf("    GMTOFF: not support\n");
+        else
+                printf("    GMTOFF: %ld    TIMEZONE: %d\n", gmtoff, gmtoff / 60 * -1);
+
+        close(fd);
+        printf("\n");
+}
+
+void print_rtc_devs(void)
+{
+        int i;
+
+        for (i = 0; i <= MAX_DEV; i++) {
+                if(strlen(rtc_devs[i].sys_path))
+                        print_rtc_dev(&rtc_devs[i]);
+        }
+}
+
+void print_rtc_dev_time(struct rtc_dev *dev)
+{
+        long int gmtoff;
+        struct rtc_time rtc_tm;
+        int fd, ret;
+
+        fd = open(dev->dev_path, O_RDONLY);
+
+        /* Read the RTC time/date */
+        ret = ioctl(fd, RTC_RD_TIME, &rtc_tm);
+        if (ret == -1)
+                perror("RTC_RD_TIME ioctl");
+
+        printf("        %s: %d-%d-%d %02d:%02d:%02d\n", dev->name,
+                rtc_tm.tm_mday, rtc_tm.tm_mon + 1, rtc_tm.tm_year + 1900,
+                rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec);
+
+        close(fd);
+}
+
+void print_rtc_devs_time(void)
+{
+        int i;
+
+        for (i = 0; i <= MAX_DEV; i++) {
+                if(strlen(rtc_devs[i].sys_path))
+                        print_rtc_dev_time(&rtc_devs[i]);
+        }
+}
+
+void time_increase(struct rtc_dev *dev, int in_min)
+{
+        struct rtc_time rtc_tm;
+        int fd, ret;
+
+        fd = open(dev->dev_path, O_RDONLY);
+
+        /* Read the RTC time/date */
+        ret = ioctl(fd, RTC_RD_TIME, &rtc_tm);
+        if (ret == -1)
+                perror("RTC_RD_TIME ioctl");
+
+        /* Increase minutes */
+        rtc_tm.tm_min += in_min;
+        if (rtc_tm.tm_min >= 60) {
+                rtc_tm.tm_hour += (rtc_tm.tm_min / 60);
+                rtc_tm.tm_min %= 60;
+                if (rtc_tm.tm_hour >= 24) {
+                        rtc_tm.tm_mday += (rtc_tm.tm_hour / 24);
+                        rtc_tm.tm_hour %= 24;
+                }
+                /* Yes, it's not perfect, only adjust to mday level for testing */
+        }
+
+        /* Set increased time */
+        ret = ioctl(fd, RTC_SET_TIME, &rtc_tm);
+        if (ret == -1)
+                perror("RTC_SET_TIME ioctl");
+
+        close(fd);
+}
+
+void time_decrease(struct rtc_dev *dev, int in_min)
+{
+        struct rtc_time rtc_tm;
+        int fd, ret;
+
+        fd = open(dev->dev_path, O_RDONLY);
+
+        /* Read the RTC time/date */
+        ret = ioctl(fd, RTC_RD_TIME, &rtc_tm);
+        if (ret == -1)
+                perror("RTC_RD_TIME ioctl");
+
+        /* Increase minutes */
+        if ((rtc_tm.tm_min - in_min) < 0) {
+                rtc_tm.tm_hour -= (in_min / 60);
+                rtc_tm.tm_min -= in_min % 60;
+                if (rtc_tm.tm_hour < 0) {
+                        rtc_tm.tm_mday += (rtc_tm.tm_hour / 24);
+                        rtc_tm.tm_hour = (rtc_tm.tm_hour % 24) * -1;
+                }
+                /* Yes, it's not perfect, only adjust to mday level for testing */
+        }
+
+        ret = ioctl(fd, RTC_SET_TIME, &rtc_tm);
+        if (ret == -1)
+                perror("RTC_SET_TIME ioctl");
+
+        close(fd);
+}
+
+void set_rtc_time_test(void)
+{
+        int i;
+
+        for (i = 0; i <= MAX_DEV; i++) {
+                struct rtc_dev *dev = &rtc_devs[i];
+
+                if(strlen(dev->sys_path) &&
+                   dev->caps & RTC_TZ_CAP) {
+                        printf("Test Target: %s(%s)\n", dev->name, dev->driver_name);
+
+                        printf("    Before Increase\n");
+                        print_rtc_devs_time();
+                        time_increase(dev, ADJUST_MIN);
+                        printf("    After Increased %d minutes\n", ADJUST_MIN);
+                        print_rtc_devs_time();
+                        printf("    Before Decrease\n");
+                        print_rtc_devs_time();
+                        time_decrease(dev, ADJUST_MIN);
+                        printf("    After Decreased %d minutes\n", ADJUST_MIN);
+                        print_rtc_devs_time();
+
+                        printf("\n\n");
+                }
+        }
+}
+
+void print_rtc_dev_tz(struct rtc_dev *dev)
+{
+        long int gmtoff;
+        struct rtc_time rtc_tm;
+        int fd, ret;
+
+        fd = open(dev->dev_path, O_RDONLY);
+
+        /* Read the GMTOFF (Seconds east of UTC) */
+        ret = ioctl(fd, RTC_RD_GMTOFF, &gmtoff);
+        if (ret == -1)
+                printf("        %s(%s): not support\n", dev->name, dev->driver_name);
+        else
+                printf("        %s(%s): GMTOFF: %ld    TIMEZONE: %d\n", dev->name, dev->driver_name, gmtoff, gmtoff / 60 * -1);
+
+        close(fd);
+}
+
+void print_rtc_devs_tz()
+{
+        int i;
+
+        for (i = 0; i <= MAX_DEV; i++) {
+                if(strlen(rtc_devs[i].sys_path))
+                        print_rtc_dev_tz(&rtc_devs[i]);
+        }
+}
+
+long int change_gmtoff(struct rtc_dev *dev, long int gmtoff_in)
+{
+        long int gmtoff = 122820;
+        struct rtc_time rtc_tm;
+        int fd, ret;
+
+        fd = open(dev->dev_path, O_RDONLY);
+
+        /* Read the GMTOFF (Seconds east of UTC) */
+        ret = ioctl(fd, RTC_RD_GMTOFF, &gmtoff);
+        if (ret == -1) {
+                printf("RTC_RD_GMTOFF fail.\n");
+                goto read_err;
+        }
+
+        ret = ioctl(fd, RTC_SET_GMTOFF, gmtoff_in);
+        if (ret == -1)
+                printf("RTC_SET_GMTOFF fail.\n");
+
+read_err:
+        close(fd);
+
+        return gmtoff;
+}
+
+void access_gmtoff_test(void)
+{
+        int i;
+
+        for (i = 0; i <= MAX_DEV; i++) {
+                struct rtc_dev *dev = &rtc_devs[i];
+
+                if(strlen(dev->sys_path) &&
+                   dev->caps & RTC_TZ_CAP) {
+                        long int orig_tz;
+
+                        printf("Test Target: %s(%s)\n", dev->name, dev->driver_name);
+                        printf("Set to Default TZ: %ld\n", DEFAULT_TZ);
+                        change_gmtoff(dev, DEFAULT_TZ);
+
+                        printf("    Before Adjust TZ\n");
+                        print_rtc_devs_tz();
+                        orig_tz = change_gmtoff(dev, ADJUST_TZ);
+                        printf("    After Adjusted TZ\n");
+                        print_rtc_devs_tz();
+                        orig_tz = change_gmtoff(dev, orig_tz);
+                        printf("    Adjusted Back\n");
+                        print_rtc_devs_tz();
+
+                        printf("\n\n");
+                }
+        }
+}
+
+int main(int argc, char **argv)
+{
+        search_rtc_dev();
+        printf("\nThis testing program will access following ioctl interface:\n");
+        printf("    RTC_RD_TIME/RTC_SET_TIME: Used to read and set RTC value.\n");
+        printf("    RTC_RD_GMTOFF/RTC_SET_GMTOFF: Used to read and set timezone, input/output is \"Seconds east of UTC\".\n");
+        printf("    RTC_CAPS_READ: Read the Timzone and Daylight capabilities of RTC interface.\n");
+
+        printf("\n======== Read Time Testing (RTC_RD_TIME/RTC_CAPS_READ) ========\n\n");
+        print_rtc_devs();
+
+        printf("\n======== Set Time Testing (RTC_SET_TIME/RTC_RD_TIME) ========\n");
+        printf("Only testing the interface supported Timezone.\n");
+        printf("This testing will increase %d minutes of RTC time then decrease it back.\n\n", ADJUST_MIN);
+        set_rtc_time_test();
+
+        printf("\n======== Access TimeZone Testing (RTC_RD_GMTOFF/RTC_SET_GMTOFF) ========\n");
+        printf("Only testing the interface supported Timezone.\n");
+        printf("Timezone of ACPI and UEFI spec: Time zone field is the number of minutes that the local time lags behind the UTC time.\n");
+        printf("                                -1440 to 1440 or 2047. Localtime = UTC - TimeZone\n");
+        printf("Timezone in GNU tm struct: Seconds east of UTC.\n");
+        printf("This testing will set time zone to Los Angeles time (-28800 Seconds east of UTC) then set it back.\n\n", ADJUST_MIN);
+        access_gmtoff_test();
+
+        return 0;
+}