linux/drivers/rtc/rtc-efi.c
Ingo Molnar 3c01b74e81 * Move efivarfs from the misc filesystem section to pseudo filesystem,
since that's a more logical and accurate place - Leif Lindholm
 
  * Update efibootmgr URL in Kconfig help - Peter Jones
 
  * Improve accuracy of EFI guid function names - Borislav Petkov
 
  * Expose firmware platform size in sysfs for the benefit of EFI boot
    loader installers and other utilities - Steve McIntyre
 
  * Cleanup __init annotations for arm64/efi code - Ard Biesheuvel
 
  * Mark the UIE as unsupported for rtc-efi - Ard Biesheuvel
 
  * Fix memory leak in error code path of runtime map code - Dan Carpenter
 
  * Improve robustness of get_memory_map() by removing assumptions on the
    size of efi_memory_desc_t (which could change in future spec
    versions) and querying the firmware instead of guessing about the
    memmap size - Ard Biesheuvel
 
  * Remove superfluous guid unparse calls - Ivan Khoronzhuk
 
  * Delete unnecessary chosen@0 DT node FDT code since was duplicated
    from code in drivers/of and is entirely unnecessary - Leif Lindholm
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQIcBAABAgAGBQJUv69oAAoJEC84WcCNIz1VEYgP/1b27WRfCXs4q/8FP+UheSDS
 nAFbGe9PjVPnxo5pA9VwPP6eNQ2zYiyNGEK1BlbQlFPZdSD1updIraA78CiF5iys
 iSYyG9xVIcTB23RZI8aJLnBXbosIUKPJZ3FORv1LPhI6Mz1rCpraEaaUlv67rUKr
 FLBG9cR7t9f/f+fJw6LOAAISGIG/4s0wQdA5/noaYkj5R5bICl2UTGtbwa0oNstb
 NUO93aKDgaG/VljpIEeG6XV96Ioz7cHjQsEaX8sTrvT0n7nPNIqSDjFJOqWKJOXl
 RsFrzyl8fFIbMuQatYv1f3efPvyH+iKOfHnHrvcjUNje0xhm7F0Bd86BkOw1a3JQ
 pNb0YUWecI0Z/8GSzN8X0JQ7cowa3wI15Z/Hfs03odTXiM6VqwFAhuz/s5DEUdKS
 U+rOPjU0ezt3G4oBB/VGgF9w5JWKfsMcsHgmLX9P+JYzKFrxggo1SXAtXUeRAqQp
 agKmUB+k6Y1baQO8efkoM7rKL2F0q1SR9QiK+16BHCCkevD23v7IFGrHm2r1xKil
 kvWlY4MkRVa4KGPxEFEDVty0HjXxImwYsxTaYVHTS7SMeoP41f6koHKB19NaB3No
 5fqn/rT1KcJuhQj/I+vAixIX4WMJkX/MQVbtKfqSaKlAiRg3eRY6ONYr0jOglfF6
 gaMuvmDd0HlV6UJvH/9L
 =iPpM
 -----END PGP SIGNATURE-----

Merge tag 'efi-next' of git://git.kernel.org/pub/scm/linux/kernel/git/mfleming/efi into x86/efi

Pull EFI updates from Matt Fleming:

" - Move efivarfs from the misc filesystem section to pseudo filesystem,
    since that's a more logical and accurate place - Leif Lindholm

  - Update efibootmgr URL in Kconfig help - Peter Jones

  - Improve accuracy of EFI guid function names - Borislav Petkov

  - Expose firmware platform size in sysfs for the benefit of EFI boot
    loader installers and other utilities - Steve McIntyre

  - Cleanup __init annotations for arm64/efi code - Ard Biesheuvel

  - Mark the UIE as unsupported for rtc-efi - Ard Biesheuvel

  - Fix memory leak in error code path of runtime map code - Dan Carpenter

  - Improve robustness of get_memory_map() by removing assumptions on the
    size of efi_memory_desc_t (which could change in future spec
    versions) and querying the firmware instead of guessing about the
    memmap size - Ard Biesheuvel

  - Remove superfluous guid unparse calls - Ivan Khoronzhuk

  - Delete unnecessary chosen@0 DT node FDT code since was duplicated
    from code in drivers/of and is entirely unnecessary - Leif Lindholm

   There's nothing super scary, mainly cleanups, and a merge from Ricardo who
   kindly picked up some patches from the linux-efi mailing list while I
   was out on annual leave in December.

   Perhaps the biggest risk is the get_memory_map() change from Ard, which
   changes the way that both the arm64 and x86 EFI boot stub build the
   early memory map. It would be good to have it bake in linux-next for a
   while.
"

Signed-off-by: Ingo Molnar <mingo@kernel.org>
2015-01-29 19:16:40 +01:00

240 lines
5.1 KiB
C

/*
* rtc-efi: RTC Class Driver for EFI-based systems
*
* Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
*
* Author: dann frazier <dannf@hp.com>
* Based on efirtc.c by Stephane Eranian
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/stringify.h>
#include <linux/time.h>
#include <linux/platform_device.h>
#include <linux/rtc.h>
#include <linux/efi.h>
#define EFI_ISDST (EFI_TIME_ADJUST_DAYLIGHT|EFI_TIME_IN_DAYLIGHT)
/*
* EFI Epoch is 1/1/1998
*/
#define EFI_RTC_EPOCH 1998
/*
* returns day of the year [0-365]
*/
static inline int
compute_yday(efi_time_t *eft)
{
/* efi_time_t.month is in the [1-12] so, we need -1 */
return rtc_year_days(eft->day, eft->month - 1, eft->year);
}
/*
* returns day of the week [0-6] 0=Sunday
*
* Don't try to provide a year that's before 1998, please !
*/
static int
compute_wday(efi_time_t *eft)
{
int y;
int ndays = 0;
if (eft->year < EFI_RTC_EPOCH) {
pr_err("EFI year < " __stringify(EFI_RTC_EPOCH) ", invalid date\n");
return -1;
}
for (y = EFI_RTC_EPOCH; y < eft->year; y++)
ndays += 365 + (is_leap_year(y) ? 1 : 0);
ndays += compute_yday(eft);
/*
* 4=1/1/1998 was a Thursday
*/
return (ndays + 4) % 7;
}
static void
convert_to_efi_time(struct rtc_time *wtime, efi_time_t *eft)
{
eft->year = wtime->tm_year + 1900;
eft->month = wtime->tm_mon + 1;
eft->day = wtime->tm_mday;
eft->hour = wtime->tm_hour;
eft->minute = wtime->tm_min;
eft->second = wtime->tm_sec;
eft->nanosecond = 0;
eft->daylight = wtime->tm_isdst ? EFI_ISDST : 0;
eft->timezone = EFI_UNSPECIFIED_TIMEZONE;
}
static bool
convert_from_efi_time(efi_time_t *eft, struct rtc_time *wtime)
{
memset(wtime, 0, sizeof(*wtime));
if (eft->second >= 60)
return false;
wtime->tm_sec = eft->second;
if (eft->minute >= 60)
return false;
wtime->tm_min = eft->minute;
if (eft->hour >= 24)
return false;
wtime->tm_hour = eft->hour;
if (!eft->day || eft->day > 31)
return false;
wtime->tm_mday = eft->day;
if (!eft->month || eft->month > 12)
return false;
wtime->tm_mon = eft->month - 1;
wtime->tm_year = eft->year - 1900;
/* day of the week [0-6], Sunday=0 */
wtime->tm_wday = compute_wday(eft);
if (wtime->tm_wday < 0)
return false;
/* day in the year [1-365]*/
wtime->tm_yday = compute_yday(eft);
switch (eft->daylight & EFI_ISDST) {
case EFI_ISDST:
wtime->tm_isdst = 1;
break;
case EFI_TIME_ADJUST_DAYLIGHT:
wtime->tm_isdst = 0;
break;
default:
wtime->tm_isdst = -1;
}
return true;
}
static int efi_read_alarm(struct device *dev, struct rtc_wkalrm *wkalrm)
{
efi_time_t eft;
efi_status_t status;
/*
* As of EFI v1.10, this call always returns an unsupported status
*/
status = efi.get_wakeup_time((efi_bool_t *)&wkalrm->enabled,
(efi_bool_t *)&wkalrm->pending, &eft);
if (status != EFI_SUCCESS)
return -EINVAL;
if (!convert_from_efi_time(&eft, &wkalrm->time))
return -EIO;
return rtc_valid_tm(&wkalrm->time);
}
static int efi_set_alarm(struct device *dev, struct rtc_wkalrm *wkalrm)
{
efi_time_t eft;
efi_status_t status;
convert_to_efi_time(&wkalrm->time, &eft);
/*
* XXX Fixme:
* As of EFI 0.92 with the firmware I have on my
* machine this call does not seem to work quite
* right
*
* As of v1.10, this call always returns an unsupported status
*/
status = efi.set_wakeup_time((efi_bool_t)wkalrm->enabled, &eft);
dev_warn(dev, "write status is %d\n", (int)status);
return status == EFI_SUCCESS ? 0 : -EINVAL;
}
static int efi_read_time(struct device *dev, struct rtc_time *tm)
{
efi_status_t status;
efi_time_t eft;
efi_time_cap_t cap;
status = efi.get_time(&eft, &cap);
if (status != EFI_SUCCESS) {
/* should never happen */
dev_err(dev, "can't read time\n");
return -EINVAL;
}
if (!convert_from_efi_time(&eft, tm))
return -EIO;
return rtc_valid_tm(tm);
}
static int efi_set_time(struct device *dev, struct rtc_time *tm)
{
efi_status_t status;
efi_time_t eft;
convert_to_efi_time(tm, &eft);
status = efi.set_time(&eft);
return status == EFI_SUCCESS ? 0 : -EINVAL;
}
static const struct rtc_class_ops efi_rtc_ops = {
.read_time = efi_read_time,
.set_time = efi_set_time,
.read_alarm = efi_read_alarm,
.set_alarm = efi_set_alarm,
};
static int __init efi_rtc_probe(struct platform_device *dev)
{
struct rtc_device *rtc;
rtc = devm_rtc_device_register(&dev->dev, "rtc-efi", &efi_rtc_ops,
THIS_MODULE);
if (IS_ERR(rtc))
return PTR_ERR(rtc);
rtc->uie_unsupported = 1;
platform_set_drvdata(dev, rtc);
return 0;
}
static struct platform_driver efi_rtc_driver = {
.driver = {
.name = "rtc-efi",
},
};
module_platform_driver_probe(efi_rtc_driver, efi_rtc_probe);
MODULE_ALIAS("platform:rtc-efi");
MODULE_AUTHOR("dann frazier <dannf@hp.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EFI RTC driver");
MODULE_ALIAS("platform:rtc-efi");