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@xxxxxxxx> --- Documentation/rtc-tz.txt | 510 ++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 510 insertions(+), 0 deletions(-) create mode 100644 Documentation/rtc-tz.txt 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@xxxxxxxx) + * + * 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; +} -- 1.6.4.2 -- To unsubscribe from this list: send the line "unsubscribe linux-efi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html