diff --git a/sys/dev/efidev/efidev.c b/sys/dev/efidev/efidev.c --- a/sys/dev/efidev/efidev.c +++ b/sys/dev/efidev/efidev.c @@ -90,6 +90,21 @@ error = efi_set_time(tm); break; } + case EFIIOC_GET_WAKETIME: + { + struct efi_waketime_ioc *wt = (struct efi_waketime_ioc *)addr; + + error = efi_get_waketime(&wt->enabled, &wt->pending, + &wt->waketime); + break; + } + case EFIIOC_SET_WAKETIME: + { + struct efi_waketime_ioc *wt = (struct efi_waketime_ioc *)addr; + + error = efi_set_waketime(wt->enabled, &wt->waketime); + break; + } case EFIIOC_VAR_GET: { struct efi_var_ioc *ev = (struct efi_var_ioc *)addr; diff --git a/sys/dev/efidev/efirt.c b/sys/dev/efidev/efirt.c --- a/sys/dev/efidev/efirt.c +++ b/sys/dev/efidev/efirt.c @@ -32,6 +32,8 @@ #include __FBSDID("$FreeBSD$"); +#include "opt_acpi.h" + #include #include #include @@ -61,6 +63,10 @@ #include #include +#ifdef DEV_ACPI +#include +#endif + #define EFI_TABLE_ALLOC_MAX 0x800000 static struct efi_systbl *efi_systbl; @@ -571,6 +577,74 @@ return (error); } +static int +get_waketime(uint8_t *enabled, uint8_t *pending, struct efi_tm *tm) +{ + struct efirt_callinfo ec; + int error; +#ifdef DEV_ACPI + UINT32 acpiRtcEnabled; +#endif + + if (efi_runtime == NULL) + return (ENXIO); + + EFI_TIME_LOCK(); + bzero(&ec, sizeof(ec)); + ec.ec_name = "rt_getwaketime"; + ec.ec_argcnt = 3; + ec.ec_arg1 = (uintptr_t)enabled; + ec.ec_arg2 = (uintptr_t)pending; + ec.ec_arg3 = (uintptr_t)tm; + ec.ec_fptr = EFI_RT_METHOD_PA(rt_getwaketime); + error = efi_call(&ec); + EFI_TIME_UNLOCK(); + +#ifdef DEV_ACPI + if (error == 0) { + error = AcpiReadBitRegister(ACPI_BITREG_RT_CLOCK_ENABLE, + &acpiRtcEnabled); + if (ACPI_SUCCESS(error)) { + *enabled = *enabled && acpiRtcEnabled; + } else + error = EIO; + } +#endif + + return (error); +} + +static int +set_waketime(uint8_t enable, struct efi_tm *tm) +{ + struct efirt_callinfo ec; + int error; + + if (efi_runtime == NULL) + return (ENXIO); + + EFI_TIME_LOCK(); + bzero(&ec, sizeof(ec)); + ec.ec_name = "rt_setwaketime"; + ec.ec_argcnt = 2; + ec.ec_arg1 = (uintptr_t)enable; + ec.ec_arg2 = (uintptr_t)tm; + ec.ec_fptr = EFI_RT_METHOD_PA(rt_setwaketime); + error = efi_call(&ec); + EFI_TIME_UNLOCK(); + +#ifdef DEV_ACPI + if (error == 0) { + error = AcpiWriteBitRegister(ACPI_BITREG_RT_CLOCK_ENABLE, + (enable != 0) ? 1 : 0); + if (ACPI_FAILURE(error)) + error = EIO; + } +#endif + + return (error); +} + static int get_time_capabilities(struct efi_tmcap *tmcap) { @@ -713,6 +787,8 @@ .get_time_capabilities = get_time_capabilities, .reset_system = reset_system, .set_time = set_time, + .get_waketime = get_waketime, + .set_waketime = set_waketime, .var_get = var_get, .var_nextname = var_nextname, .var_set = var_set, diff --git a/sys/sys/efi.h b/sys/sys/efi.h --- a/sys/sys/efi.h +++ b/sys/sys/efi.h @@ -254,6 +254,9 @@ int (*get_time_capabilities)(struct efi_tmcap *); int (*reset_system)(enum efi_reset); int (*set_time)(struct efi_tm *); + int (*get_waketime)(uint8_t *enabled, uint8_t *pending, + struct efi_tm *tm); + int (*set_waketime)(uint8_t enable, struct efi_tm *tm); int (*var_get)(uint16_t *, struct uuid *, uint32_t *, size_t *, void *); int (*var_nextname)(size_t *, uint16_t *, struct uuid *); @@ -319,6 +322,21 @@ return (active_efi_ops->set_time(tm)); } +static inline int efi_get_waketime(uint8_t *enabled, uint8_t *pending, + struct efi_tm *tm) +{ + if (active_efi_ops->get_waketime == NULL) + return (ENXIO); + return (active_efi_ops->get_waketime(enabled, pending, tm)); +} + +static inline int efi_set_waketime(uint8_t enable, struct efi_tm *tm) +{ + if (active_efi_ops->set_waketime == NULL) + return (ENXIO); + return (active_efi_ops->set_waketime(enable, tm)); +} + static inline int efi_var_get(uint16_t *name, struct uuid *vendor, uint32_t *attrib, size_t *datasize, void *data) { diff --git a/sys/sys/efiio.h b/sys/sys/efiio.h --- a/sys/sys/efiio.h +++ b/sys/sys/efiio.h @@ -50,11 +50,20 @@ size_t datasize; /* Number of *bytes* in the data */ }; +struct efi_waketime_ioc +{ + struct efi_tm waketime; + uint8_t enabled; + uint8_t pending; +}; + #define EFIIOC_GET_TABLE _IOWR('E', 1, struct efi_get_table_ioc) #define EFIIOC_GET_TIME _IOR('E', 2, struct efi_tm) #define EFIIOC_SET_TIME _IOW('E', 3, struct efi_tm) #define EFIIOC_VAR_GET _IOWR('E', 4, struct efi_var_ioc) #define EFIIOC_VAR_NEXT _IOWR('E', 5, struct efi_var_ioc) #define EFIIOC_VAR_SET _IOWR('E', 6, struct efi_var_ioc) +#define EFIIOC_GET_WAKETIME _IOR('E', 7, struct efi_waketime_ioc) +#define EFIIOC_SET_WAKETIME _IOW('E', 8, struct efi_waketime_ioc) #endif /* _SYS_EFIIO_H_ */ diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile --- a/usr.sbin/Makefile +++ b/usr.sbin/Makefile @@ -129,7 +129,7 @@ .endif SUBDIR.${MK_CXGBETOOL}+= cxgbetool SUBDIR.${MK_DIALOG}+= bsdconfig -SUBDIR.${MK_EFI}+= efivar efidp efibootmgr efitable +SUBDIR.${MK_EFI}+= efivar efidp efibootmgr efitable efiwake .if ${MK_OPENSSL} != "no" SUBDIR.${MK_EFI}+= uefisign .endif diff --git a/usr.sbin/efiwake/Makefile b/usr.sbin/efiwake/Makefile new file mode 100644 --- /dev/null +++ b/usr.sbin/efiwake/Makefile @@ -0,0 +1,13 @@ +# $FreeBSD$ + +PACKAGE= efi-tools + +PROG= efiwake +MAN= + +SRCS= efiwake.c + +EFIBOOT=${SRCTOP}/stand/efi +CFLAGS+=-I${EFIBOOT}/include + +.include diff --git a/usr.sbin/efiwake/efiwake.c b/usr.sbin/efiwake/efiwake.c new file mode 100644 --- /dev/null +++ b/usr.sbin/efiwake/efiwake.c @@ -0,0 +1,134 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2023 Johannes Totz + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void usage(void) +{ + printf("Usage:\n" + " efiwake -- print out current EFI time and wake time\n" + " efiwake -d -- disable wake time\n" + " efiwake -e yyyy-mm-ddThh:mm:ss -- enable wake time\n" + ); + + exit(EX_USAGE); +} + +int main(int argc, char **argv) +{ + struct efi_waketime_ioc waketime; + int error, ch; + bool enable = false, disable = false; + + memset(&waketime, 0, sizeof(waketime)); + + while ((ch = getopt(argc, argv, "de:")) != -1) { + switch (ch) { + case 'd': + disable = true; + break; + case 'e': + if (sscanf(optarg, + "%hu-%02hhu-%02hhuT%02hhu:%02hhu:%02hhu", + &waketime.waketime.tm_year, &waketime.waketime.tm_mon, + &waketime.waketime.tm_mday, &waketime.waketime.tm_hour, + &waketime.waketime.tm_min, &waketime.waketime.tm_sec) + != 6) { + usage(); + } + enable = true; + break; + case '?': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc > 0) + usage(); + if (disable && enable) + usage(); + + int efi_fd = open("/dev/efi", O_RDWR); + if (efi_fd < 0) + err(EX_OSERR, "cannot open /dev/efi"); + + struct efi_tm now; + error = ioctl(efi_fd, EFIIOC_GET_TIME, &now); + if (error != 0) + err(EX_OSERR, "cannot get EFI time"); + + /* EFI's time can be different from kernel's time. */ + printf("Current EFI time: %u-%02u-%02uT%02u:%02u:%02u\n", + now.tm_year, now.tm_mon, now.tm_mday, now.tm_hour, now.tm_min, + now.tm_sec); + + if (disable) { + /* It's tempting to preserve the current timer value. + * However, wonky EFI implementations sometimes return bogus + * dates for the wake timer and would then fail disabling it + * here. + * A safe timer value is the current EFI time. + */ + waketime.waketime = now; + waketime.enabled = 0; + error = ioctl(efi_fd, EFIIOC_SET_WAKETIME, &waketime); + if (error != 0) + err(EX_OSERR, "cannot disable EFI wake time"); + } + if (enable) { + waketime.enabled = 1; + error = ioctl(efi_fd, EFIIOC_SET_WAKETIME, &waketime); + if (error != 0) + err(EX_OSERR, "cannot enable EFI wake time"); + } + + /* Confirm to user what the wake time has been set to. */ + error = ioctl(efi_fd, EFIIOC_GET_WAKETIME, &waketime); + if (error != 0) + err(EX_OSERR, "cannot get EFI wake time"); + + printf("EFI wake time: %u-%02u-%02uT%02u:%02u:%02u; enabled=%i, pending=%i\n", + waketime.waketime.tm_year, waketime.waketime.tm_mon, + waketime.waketime.tm_mday, waketime.waketime.tm_hour, + waketime.waketime.tm_min, waketime.waketime.tm_sec, + waketime.enabled, waketime.pending); + + close(efi_fd); + return (0); +}