[systemd-devel] Support for UEFI's OsIndications to reboot into firmware menus

Peter Jones pjones at redhat.com
Wed Jan 16 12:46:32 PST 2013


Hi!

Attached is a preliminary patch to add support for UEFI's
"OsIndications" feature.  This feature allows the operating system to
indicate to the system firmware that on the next boot up, instead of
starting the operating system, the system should invoke the firmware
configuration menus.

This is part of work I'm doing to let us run reasonably with "Windows 8
Fast Boot" features turned on, which aim at making normal boots not
initialize the keyboard, or indeed the entire USB stack (unless it's
used in a already-defined boot option) thus saving several seconds during
boot up.  Obviously that means not getting keyboard input from the firmware,
bootloaders, and so forth, which means we need alternate ways to select
those types of options.

The patch adds a couple of things:

"shutdown --firmware" - this allows us to reboot into the firmware using
the shutdown command.  I've tested this and it works.  Note that right
now "shutdown -c" won't cancel the indication to the firmware.

dbus stuff - I've probably gotten this wrong in a great many ways.  At
the least I'd like feedback on dbus method names, any coding style issues
you may see, if/how to add this to any test suite you might have, and
how to test it at all, since there are by definition no current
consumers of this. Also obviously I could have made some errors ;)

Please Cc me on any issues, as I'm not on the mailing list.
-- 
        Peter
-------------- next part --------------
>From a75c1ef3ca50b8554b3f7f673bad11d22163b7ee Mon Sep 17 00:00:00 2001
From: Peter Jones <pjones at redhat.com>
Date: Thu, 16 Aug 2012 13:44:02 -0400
Subject: [PATCH] Add "shutdown --firmware", which reboots into the firmware
 menu.

UEFI 2.3.1 and newer supports the OS asking a system to reboot into the
firmware setup menu. This in turn allows firmware vendors to remove the
startup delay for their "press F17 to enter firmware config" screen, so
we should probably support it somehow.

Signed-off-by: Peter Jones <systemd-owner at fedoraproject.org>
---
 Makefile.am                                |   4 +-
 man/shutdown.xml                           |   8 ++
 src/core/dbus-manager.c                    |  16 +++
 src/core/main.c                            |   4 +-
 src/core/manager.c                         |   6 +-
 src/core/manager.h                         |   1 +
 src/core/shutdown.c                        |  16 ++-
 src/core/special.h                         |   1 +
 src/login/logind-action.c                  |   9 +-
 src/login/logind-action.h                  |   1 +
 src/login/logind-dbus.c                    |  34 +++++-
 src/login/org.freedesktop.login1.conf      |   8 ++
 src/login/org.freedesktop.login1.policy.in |  32 ++++++
 src/shared/efivars.c                       | 163 +++++++++++++++++++++++++++++
 src/shared/efivars.h                       |  61 +++++++++++
 src/systemctl/systemctl.c                  |  35 ++++++-
 16 files changed, 388 insertions(+), 11 deletions(-)
 create mode 100644 src/shared/efivars.c
 create mode 100644 src/shared/efivars.h

diff --git a/Makefile.am b/Makefile.am
index 28d8d44..39552ca 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -810,7 +810,9 @@ libsystemd_shared_la_SOURCES = \
 	src/shared/time-dst.c \
 	src/shared/time-dst.h \
 	src/shared/calendarspec.c \
-	src/shared/calendarspec.h
+	src/shared/calendarspec.h \
+	src/shared/efivars.c \
+	src/shared/efivars.h
 
 libsystemd_shared_la_LIBADD = libsystemd-daemon.la
 
diff --git a/man/shutdown.xml b/man/shutdown.xml
index d54fcb2..d1d3b11 100644
--- a/man/shutdown.xml
+++ b/man/shutdown.xml
@@ -122,6 +122,14 @@
                         </varlistentry>
 
                         <varlistentry>
+                                <term><option>-f</option></term>
+                                <term><option>--firmware</option></term>
+
+                                <listitem><para>Reboot the
+                                machine into the firmware menu.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
                                 <term><option>-h</option></term>
 
                                 <listitem><para>Equivalent to
diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
index 859fa1a..ee07bbb 100644
--- a/src/core/dbus-manager.c
+++ b/src/core/dbus-manager.c
@@ -131,6 +131,7 @@
         "  <method name=\"PowerOff\"/>\n"                               \
         "  <method name=\"Halt\"/>\n"                                   \
         "  <method name=\"KExec\"/>\n"                                  \
+        "  <method name=\"Firmware\"/>\n"                               \
         "  <method name=\"SwitchRoot\">\n"                              \
         "   <arg name=\"new_root\" type=\"s\" direction=\"in\"/>\n"     \
         "   <arg name=\"init\" type=\"s\" direction=\"in\"/>\n"         \
@@ -1203,6 +1204,21 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
 
                 m->exit_code = MANAGER_REBOOT;
 
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "Firmware")) {
+
+                SELINUX_ACCESS_CHECK(connection, message, "firmware");
+
+                if (m->running_as != SYSTEMD_SYSTEM) {
+                        dbus_set_error(&error, BUS_ERROR_NOT_SUPPORTED, "Reboot into firmware is only supported for system managers.");
+                        return bus_send_error_reply(connection, message, &error, -ENOTSUP);
+                }
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+                m->exit_code = MANAGER_FIRMWARE;
+
         } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "PowerOff")) {
 
                 SELINUX_ACCESS_CHECK(connection, message, "halt");
diff --git a/src/core/main.c b/src/core/main.c
index 1ee3c9c..a4ff87b 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -1776,6 +1776,7 @@ int main(int argc, char *argv[]) {
                         goto finish;
 
                 case MANAGER_REBOOT:
+                case MANAGER_FIRMWARE:
                 case MANAGER_POWEROFF:
                 case MANAGER_HALT:
                 case MANAGER_KEXEC: {
@@ -1783,7 +1784,8 @@ int main(int argc, char *argv[]) {
                                 [MANAGER_REBOOT] = "reboot",
                                 [MANAGER_POWEROFF] = "poweroff",
                                 [MANAGER_HALT] = "halt",
-                                [MANAGER_KEXEC] = "kexec"
+                                [MANAGER_KEXEC] = "kexec",
+                                [MANAGER_FIRMWARE] = "firmware"
                         };
 
                         assert_se(shutdown_verb = table[m->exit_code]);
diff --git a/src/core/manager.c b/src/core/manager.c
index df0fd63..0f701d5 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -1318,7 +1318,8 @@ static int manager_process_signal_fd(Manager *m) {
                                 [3] = SPECIAL_HALT_TARGET,
                                 [4] = SPECIAL_POWEROFF_TARGET,
                                 [5] = SPECIAL_REBOOT_TARGET,
-                                [6] = SPECIAL_KEXEC_TARGET
+                                [6] = SPECIAL_KEXEC_TARGET,
+                                [7] = SPECIAL_FIRMWARE_TARGET
                         };
 
                         /* Starting SIGRTMIN+13, so that target halt and system halt are 10 apart */
@@ -1326,7 +1327,8 @@ static int manager_process_signal_fd(Manager *m) {
                                 [0] = MANAGER_HALT,
                                 [1] = MANAGER_POWEROFF,
                                 [2] = MANAGER_REBOOT,
-                                [3] = MANAGER_KEXEC
+                                [3] = MANAGER_KEXEC,
+                                [4] = MANAGER_FIRMWARE
                         };
 
                         if ((int) sfsi.ssi_signo >= SIGRTMIN+0 &&
diff --git a/src/core/manager.h b/src/core/manager.h
index cc4edf8..27f5db2 100644
--- a/src/core/manager.h
+++ b/src/core/manager.h
@@ -45,6 +45,7 @@ typedef enum ManagerExitCode {
         MANAGER_HALT,
         MANAGER_KEXEC,
         MANAGER_SWITCH_ROOT,
+        MANAGER_FIRMWARE,
         _MANAGER_EXIT_CODE_MAX,
         _MANAGER_EXIT_CODE_INVALID = -1
 } ManagerExitCode;
diff --git a/src/core/shutdown.c b/src/core/shutdown.c
index 0b0e0c3..432757e 100644
--- a/src/core/shutdown.c
+++ b/src/core/shutdown.c
@@ -45,6 +45,7 @@
 #include "virt.h"
 #include "watchdog.h"
 #include "killall.h"
+#include "efivars.h"
 
 #define FINALIZE_ATTEMPTS 50
 
@@ -164,7 +165,20 @@ int main(int argc, char *argv[]) {
                 cmd = RB_HALT_SYSTEM;
         else if (streq(argv[1], "kexec"))
                 cmd = LINUX_REBOOT_CMD_KEXEC;
-        else {
+        else if (streq(argv[1], "firmware")) {
+                if (!has_uefi_firmware_reboot()) {
+                        log_error("System does not support rebooting into firmware menus.");
+                        r = -EINVAL;
+                        goto error;
+                }
+                r = set_uefi_firmware_reboot(1);
+		if (r < 0) {
+			log_error("Could not set EFI firmware variable.");
+			r = -EINVAL;
+			goto error;
+		}
+                cmd = RB_AUTOBOOT;
+        } else {
                 log_error("Unknown action '%s'.", argv[1]);
                 r = -EINVAL;
                 goto error;
diff --git a/src/core/special.h b/src/core/special.h
index 15aa6ca..a028e9d 100644
--- a/src/core/special.h
+++ b/src/core/special.h
@@ -38,6 +38,7 @@
 #define SPECIAL_SUSPEND_TARGET "suspend.target"
 #define SPECIAL_HIBERNATE_TARGET "hibernate.target"
 #define SPECIAL_HYBRID_SLEEP_TARGET "hybrid-sleep.target"
+#define SPECIAL_FIRMWARE_TARGET "firmware.target"
 
 /* Special boot targets */
 #define SPECIAL_RESCUE_TARGET "rescue.target"
diff --git a/src/login/logind-action.c b/src/login/logind-action.c
index e1517d6..be2bff5 100644
--- a/src/login/logind-action.c
+++ b/src/login/logind-action.c
@@ -40,7 +40,8 @@ int manager_handle_action(
                 [HANDLE_KEXEC] = "Rebooting via kexec...",
                 [HANDLE_SUSPEND] = "Suspending...",
                 [HANDLE_HIBERNATE] = "Hibernating...",
-                [HANDLE_HYBRID_SLEEP] = "Hibernating and suspending..."
+                [HANDLE_HYBRID_SLEEP] = "Hibernating and suspending...",
+                [HANDLE_FIRMWARE] "Rebooting into firmware menu..."
         };
 
         static const char * const target_table[_HANDLE_ACTION_MAX] = {
@@ -50,7 +51,8 @@ int manager_handle_action(
                 [HANDLE_KEXEC] = SPECIAL_KEXEC_TARGET,
                 [HANDLE_SUSPEND] = SPECIAL_SUSPEND_TARGET,
                 [HANDLE_HIBERNATE] = SPECIAL_HIBERNATE_TARGET,
-                [HANDLE_HYBRID_SLEEP] = SPECIAL_HYBRID_SLEEP_TARGET
+                [HANDLE_HYBRID_SLEEP] = SPECIAL_HYBRID_SLEEP_TARGET,
+                [HANDLE_FIRMWARE] = SPECIAL_FIRMWARE_TARGET
         };
 
         DBusError error;
@@ -134,7 +136,8 @@ static const char* const handle_action_table[_HANDLE_ACTION_MAX] = {
         [HANDLE_SUSPEND] = "suspend",
         [HANDLE_HIBERNATE] = "hibernate",
         [HANDLE_HYBRID_SLEEP] = "hybrid-sleep",
-        [HANDLE_LOCK] = "lock"
+        [HANDLE_LOCK] = "lock",
+        [HANDLE_FIRMWARE] = "firmware"
 };
 
 DEFINE_STRING_TABLE_LOOKUP(handle_action, HandleAction);
diff --git a/src/login/logind-action.h b/src/login/logind-action.h
index 7ab4464..9909436 100644
--- a/src/login/logind-action.h
+++ b/src/login/logind-action.h
@@ -32,6 +32,7 @@ typedef enum HandleAction {
         HANDLE_HIBERNATE,
         HANDLE_HYBRID_SLEEP,
         HANDLE_LOCK,
+        HANDLE_FIRMWARE,
         _HANDLE_ACTION_MAX,
         _HANDLE_ACTION_INVALID = -1
 } HandleAction;
diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c
index 77a06f2..46f2bb8 100644
--- a/src/login/logind-dbus.c
+++ b/src/login/logind-dbus.c
@@ -139,6 +139,9 @@
         "  <method name=\"Reboot\">\n"                                  \
         "   <arg name=\"interactive\" type=\"b\" direction=\"in\"/>\n"  \
         "  </method>\n"                                                 \
+        "  <method name=\"Firmware\">\n"                                \
+        "   <arg name=\"interactive\" type=\"b\" direction=\"in\"/>\n"  \
+        "  </method>\n"                                                 \
         "  <method name=\"Suspend\">\n"                                 \
         "   <arg name=\"interactive\" type=\"b\" direction=\"in\"/>\n"  \
         "  </method>\n"                                                 \
@@ -154,6 +157,9 @@
         "  <method name=\"CanReboot\">\n"                               \
         "   <arg name=\"result\" type=\"s\" direction=\"out\"/>\n"      \
         "  </method>\n"                                                 \
+        "  <method name=\"CanFirmware\">\n"                               \
+        "   <arg name=\"result\" type=\"s\" direction=\"out\"/>\n"      \
+        "  </method>\n"                                                 \
         "  <method name=\"CanSuspend\">\n"                              \
         "   <arg name=\"result\" type=\"s\" direction=\"out\"/>\n"      \
         "  </method>\n"                                                 \
@@ -1200,6 +1206,9 @@ static int bus_manager_log_shutdown(
         } else if (streq(unit_name, SPECIAL_KEXEC_TARGET)) {
                 p = "MESSAGE=System is rebooting with kexec.";
                 q = "SHUTDOWN=kexec";
+        } else if (streq(unit_name, SPECIAL_FIRMWARE_TARGET)) {
+                p = "MESSAGE=System is rebooting.";
+                q = "SHUTDOWN=firmware";
         } else {
                 p = "MESSAGE=System is shutting down.";
                 q = NULL;
@@ -2109,7 +2118,18 @@ static DBusHandlerResult manager_message_handler(
                                 &error, &reply);
                 if (r < 0)
                         return bus_send_error_reply(connection, message, &error, r);
-
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "Firmware")) {
+                r = bus_manager_do_shutdown_or_sleep(
+                                m, connection, message,
+                                SPECIAL_FIRMWARE_TARGET,
+                                INHIBIT_SHUTDOWN,
+                                "org.freedesktop.login1.firmware",
+                                "org.freedesktop.login1.firmware-multiple-sessions",
+                                "org.freedesktop.login1.firmware-ignore-inhibit",
+                                NULL, NULL,
+                                &error, &reply);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, &error, r);
         } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "Suspend")) {
                 r = bus_manager_do_shutdown_or_sleep(
                                 m, connection, message,
@@ -2171,7 +2191,17 @@ static DBusHandlerResult manager_message_handler(
                                 &error, &reply);
                 if (r < 0)
                         return bus_send_error_reply(connection, message, &error, r);
-
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "CanFirmware")) {
+                r = bus_manager_can_shutdown_or_sleep(
+                                m, connection, message,
+                                INHIBIT_SHUTDOWN,
+                                "org.freedesktop.login1.firmware",
+                                "org.freedesktop.login1.firmware-multiple-sessions",
+                                "org.freedesktop.login1.firmware-ignore-inhibit",
+                                NULL, NULL,
+                                &error, &reply);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, &error, r);
         } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Manager", "CanSuspend")) {
                 r = bus_manager_can_shutdown_or_sleep(
                                 m, connection, message,
diff --git a/src/login/org.freedesktop.login1.conf b/src/login/org.freedesktop.login1.conf
index 6c1f2f5..2b9bf7c 100644
--- a/src/login/org.freedesktop.login1.conf
+++ b/src/login/org.freedesktop.login1.conf
@@ -94,6 +94,10 @@
 
                 <allow send_destination="org.freedesktop.login1"
                        send_interface="org.freedesktop.login1.Manager"
+                       send_member="Firmware"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
                        send_member="Suspend"/>
 
                 <allow send_destination="org.freedesktop.login1"
@@ -114,6 +118,10 @@
 
                 <allow send_destination="org.freedesktop.login1"
                        send_interface="org.freedesktop.login1.Manager"
+                       send_member="CanFirmware"/>
+
+                <allow send_destination="org.freedesktop.login1"
+                       send_interface="org.freedesktop.login1.Manager"
                        send_member="CanSuspend"/>
 
                 <allow send_destination="org.freedesktop.login1"
diff --git a/src/login/org.freedesktop.login1.policy.in b/src/login/org.freedesktop.login1.policy.in
index b5f5db4..1764e25 100644
--- a/src/login/org.freedesktop.login1.policy.in
+++ b/src/login/org.freedesktop.login1.policy.in
@@ -206,6 +206,38 @@
                 <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.reboot</annotate>
         </action>
 
+        <action id="org.freedesktop.login1.firmware">
+                <_description>Reboot the system into firmware menus</_description>
+                <_message>Authentication is required for rebooting the system.</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>yes</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.login1.firmware-multiple-sessions">
+                <_description>Reboot the system into firmware menus while other users are logged in</_description>
+                <_message>Authentication is required for rebooting the system while other users are logged in.</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+                <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.firmware</annotate>
+        </action>
+
+        <action id="org.freedesktop.login1.firmware-ignore-inhibit">
+                <_description>Reboot the system into firmware menus while an application asked to inhibit it</_description>
+                <_message>Authentication is required for rebooting the system while an application asked to inhibit it.</_message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+                <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.firmware</annotate>
+        </action>
+
         <action id="org.freedesktop.login1.suspend">
                 <_description>Suspend the system</_description>
                 <_message>Authentication is required for suspending the system.</_message>
diff --git a/src/shared/efivars.c b/src/shared/efivars.c
new file mode 100644
index 0000000..1dc7754
--- /dev/null
+++ b/src/shared/efivars.c
@@ -0,0 +1,163 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Red Hat, Inc.
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "efivars.h"
+
+int uefi_get_variable(efi_guid_t guid, const char *name, size_t size, void *data)
+{
+        char *path = NULL;
+        int rc;
+        FILE *f;
+
+        rc = asprintf(&path, "/sys/firmware/efi/vars/%s-%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x/data", name, guid.a, guid.b, guid.c, guid.d[0], guid.d[1], guid.d[2], guid.d[3], guid.d[4], guid.d[5], guid.d[6], guid.d[7]);
+        if (rc < 0)
+                return rc;
+
+        f = fopen(path, "r");
+        free(path);
+        if (!f)
+                return -1;
+
+	memset(data, '\0', size);
+        rc = fread(data, size, 1, f);
+        fclose(f);
+        if (rc != 1)
+                return -1;
+
+        return 0;
+}
+
+int uefi_del_variable(efi_guid_t guid, const char *name)
+{
+        char *path = NULL;
+        int rc;
+        FILE *f;
+        struct efi_variable var;
+
+        rc = asprintf(&path, "/sys/firmware/efi/vars/%s-%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x/raw_var", name, guid.a, guid.b, guid.c, guid.d[0], guid.d[1], guid.d[2], guid.d[3], guid.d[4], guid.d[5], guid.d[6], guid.d[7]);
+        if (rc < 0)
+                return rc;
+
+        f = fopen(path, "r");
+        free(path);
+        if (!f)
+                return -1;
+
+        rc = fread(&var, sizeof(var), 1, f);
+        fclose(f);
+        if (rc != 1)
+                return -1;
+
+        f = fopen("/sys/firmware/efi/vars/del_var", "w");
+        if (!f)
+                return -1;
+
+        rc = fwrite(&var, sizeof(var), 1, f);
+        fclose(f);
+        if (rc != 1)
+                return -1;
+
+        return 0;
+}
+
+int uefi_set_variable(efi_guid_t guid, const char *name, size_t size, void *data)
+{
+        char *path = NULL;
+        int rc;
+        struct efi_variable var;
+        FILE *f;
+        unsigned int i,j;
+
+        if (strlen(name) > 1024 || size > 1024)
+                return -1;
+
+        rc = asprintf(&path, "/sys/firmware/efi/vars/%s-%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x/raw_var", name, guid.a, guid.b, guid.c, guid.d[0], guid.d[1], guid.d[2], guid.d[3], guid.d[4], guid.d[5], guid.d[6], guid.d[7]);
+        if (rc < 0)
+                return rc;
+
+        if (!access(path, F_OK)) {
+                rc = uefi_del_variable(guid, name);
+                if (rc < 0)
+                        return rc;
+        }
+        free(path);
+
+        memset(var.VariableName, '\0', sizeof(var.VariableName));
+        for (i = 0; i < strlen(name); i++)
+                var.VariableName[i] = name[i];
+
+        var.VendorGuid = guid;
+        var.DataSize = size;
+        memcpy(var.Data, data, size);
+        var.Status = 0;
+        var.Attributes = EFI_VARIABLE_NON_VOLATILE |
+                         EFI_VARIABLE_BOOTSERVICE_ACCESS |
+                         EFI_VARIABLE_RUNTIME_ACCESS;
+
+        f = fopen("/sys/firmware/efi/vars/new_var", "w");
+        if (!f)
+                return -1;
+
+        rc = fwrite(&var, sizeof(var), 1, f);
+        fclose(f);
+        if (rc != 1)
+                return -1;
+
+        return 0;
+}
+
+int has_uefi_firmware_reboot(void)
+{
+        uint64_t value = 0;
+
+        int rc = uefi_get_variable(EFI_GLOBAL_GUID,
+                                   "OsIndicationsSupported",
+                                   sizeof (value), &value);
+        if (rc < 0)
+                return rc;
+
+        return value & EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
+}
+
+int set_uefi_firmware_reboot(uint8_t set)
+{
+        uint64_t value;
+        int rc;
+
+        uefi_get_variable(EFI_GLOBAL_GUID, "OsIndicationsSupported",
+                          sizeof (value), &value);
+
+        if (set)
+                value |= EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
+        else
+                value = value & ~EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
+
+        rc = uefi_set_variable(EFI_GLOBAL_GUID, "OsIndications",
+                               sizeof(value), &value);
+        return rc;
+}
diff --git a/src/shared/efivars.h b/src/shared/efivars.h
new file mode 100644
index 0000000..a33ad3a
--- /dev/null
+++ b/src/shared/efivars.h
@@ -0,0 +1,61 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooefivarshfoo
+#define fooefivarshfoo
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010-2012 Red Hat, Inc.
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct {
+        uint32_t        a;
+        uint16_t        b;
+        uint16_t        c;
+        uint8_t         d[8];
+} efi_guid_t;
+
+#define EFI_GUID(a,b,c,d0,d1,d2,d3,d4,d5,d6,d7) \
+((efi_guid_t) {(a), (b), (c), { (d0), (d1), (d2), (d3), (d4), (d5), (d6), (d7) }})
+#define EFI_GLOBAL_GUID EFI_GUID(0x8be4df61,0x93ca,0x11d2,0xaa,0x0d,0x00,0xe0,0x98,0x03,0x2b,0x8c)
+
+struct efi_variable {
+        uint16_t        VariableName[1024/sizeof(uint16_t)];
+        efi_guid_t      VendorGuid;
+        unsigned long   DataSize;
+        uint8_t         Data[1024];
+        unsigned long   Status;
+        uint32_t        Attributes;
+} __attribute__((packed));
+
+#define EFI_VARIABLE_NON_VOLATILE                          0x0000000000000001
+#define EFI_VARIABLE_BOOTSERVICE_ACCESS                    0x0000000000000002
+#define EFI_VARIABLE_RUNTIME_ACCESS                        0x0000000000000004
+#define EFI_VARIABLE_HARDWARE_ERROR_RECORD                 0x0000000000000008
+#define EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS            0x0000000000000010
+#define EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS 0x0000000000000020
+#define EFI_VARIABLE_APPEND_WRITE                          0x0000000000000040
+
+#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001
+
+int uefi_get_variable(efi_guid_t guid, const char *name, size_t size, void *data);
+int uefi_del_variable(efi_guid_t guid, const char *name);
+int uefi_set_variable(efi_guid_t guid, const char *name, size_t size, void *data);
+int has_uefi_firmware_reboot(void);
+int set_uefi_firmware_reboot(uint8_t set);
+
+#endif
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index 2ebfff8..a9dd1d5 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -65,6 +65,7 @@
 #include "logs-show.h"
 #include "path-util.h"
 #include "socket-util.h"
+#include "efivars.h"
 
 static const char *arg_type = NULL;
 static const char *arg_load_state = NULL;
@@ -96,6 +97,7 @@ static enum action {
         ACTION_HALT,
         ACTION_POWEROFF,
         ACTION_REBOOT,
+        ACTION_FIRMWARE,
         ACTION_KEXEC,
         ACTION_EXIT,
         ACTION_SUSPEND,
@@ -225,6 +227,7 @@ static void warn_wall(enum action a) {
         static const char *table[_ACTION_MAX] = {
                 [ACTION_HALT]            = "The system is going down for system halt NOW!",
                 [ACTION_REBOOT]          = "The system is going down for reboot NOW!",
+                [ACTION_FIRMWARE]        = "The system is going down for reboot NOW!",
                 [ACTION_POWEROFF]        = "The system is going down for power-off NOW!",
                 [ACTION_KEXEC]           = "The system is going down for kexec reboot NOW!",
                 [ACTION_RESCUE]          = "The system is going down to rescue mode NOW!",
@@ -1608,6 +1611,8 @@ static enum action verb_to_action(const char *verb) {
                 return ACTION_POWEROFF;
         else if (streq(verb, "reboot"))
                 return ACTION_REBOOT;
+        else if (streq(verb, "firmware"))
+                return ACTION_FIRMWARE;
         else if (streq(verb, "kexec"))
                 return ACTION_KEXEC;
         else if (streq(verb, "rescue"))
@@ -1634,6 +1639,7 @@ static int start_unit(DBusConnection *bus, char **args) {
                 [ACTION_HALT] = SPECIAL_HALT_TARGET,
                 [ACTION_POWEROFF] = SPECIAL_POWEROFF_TARGET,
                 [ACTION_REBOOT] = SPECIAL_REBOOT_TARGET,
+                [ACTION_FIRMWARE] = SPECIAL_FIRMWARE_TARGET,
                 [ACTION_KEXEC] = SPECIAL_KEXEC_TARGET,
                 [ACTION_RUNLEVEL2] = SPECIAL_RUNLEVEL2_TARGET,
                 [ACTION_RUNLEVEL3] = SPECIAL_RUNLEVEL3_TARGET,
@@ -1769,6 +1775,10 @@ static int reboot_with_logind(DBusConnection *bus, enum action a) {
                 method = "Reboot";
                 break;
 
+        case ACTION_FIRMWARE:
+                method = "Firmware";
+                break;
+
         case ACTION_POWEROFF:
                 method = "PowerOff";
                 break;
@@ -1820,13 +1830,15 @@ static int start_special(DBusConnection *bus, char **args) {
         if (arg_force >= 2 &&
             (a == ACTION_HALT ||
              a == ACTION_POWEROFF ||
-             a == ACTION_REBOOT))
+             a == ACTION_REBOOT ||
+             a == ACTION_FIRMWARE))
                 halt_now(a);
 
         if (arg_force >= 1 &&
             (a == ACTION_HALT ||
              a == ACTION_POWEROFF ||
              a == ACTION_REBOOT ||
+             a == ACTION_FIRMWARE ||
              a == ACTION_KEXEC ||
              a == ACTION_EXIT))
                 return daemon_reload(bus, args);
@@ -1835,6 +1847,7 @@ static int start_special(DBusConnection *bus, char **args) {
         if (geteuid() != 0 &&
             (a == ACTION_POWEROFF ||
              a == ACTION_REBOOT ||
+             a == ACTION_FIRMWARE ||
              a == ACTION_SUSPEND ||
              a == ACTION_HIBERNATE ||
              a == ACTION_HYBRID_SLEEP)) {
@@ -4074,6 +4087,7 @@ static int shutdown_help(void) {
                "  -H --halt      Halt the machine\n"
                "  -P --poweroff  Power-off the machine\n"
                "  -r --reboot    Reboot the machine\n"
+               "  -f --firmware  Reboot the machine into the firmware menu\n"
                "  -h             Equivalent to --poweroff, overridden by --halt\n"
                "  -k             Don't halt/power-off/reboot, just send warnings\n"
                "     --no-wall   Don't send wall message before halt/power-off/reboot\n"
@@ -4525,6 +4539,7 @@ static int shutdown_parse_argv(int argc, char *argv[]) {
                 { "halt",      no_argument,       NULL, 'H'         },
                 { "poweroff",  no_argument,       NULL, 'P'         },
                 { "reboot",    no_argument,       NULL, 'r'         },
+                { "firmware",  no_argument,       NULL, 'f'         },
                 { "kexec",     no_argument,       NULL, 'K'         }, /* not documented extension */
                 { "no-wall",   no_argument,       NULL, ARG_NO_WALL },
                 { NULL,        0,                 NULL, 0           }
@@ -4557,6 +4572,10 @@ static int shutdown_parse_argv(int argc, char *argv[]) {
                                 arg_action = ACTION_REBOOT;
                         break;
 
+                case 'f':
+                        arg_action = ACTION_FIRMWARE;
+                        break;
+
                 case 'K':
                         arg_action = ACTION_KEXEC;
                         break;
@@ -5362,6 +5381,20 @@ int main(int argc, char*argv[]) {
                 r = systemctl_main(bus, argc, argv, &error);
                 break;
 
+        case ACTION_FIRMWARE:
+                if (!has_uefi_firmware_reboot()) {
+                        log_error("System does not support rebooting into firmware menus.\n");
+                        r = -EINVAL;
+                        break;
+                }
+                r = set_uefi_firmware_reboot(1);
+		if (r < 0) {
+			log_error("Could not set EFI firmware variable.");
+			r = -EINVAL;
+			break;
+		}
+
+                /* fall through to ARG_REBOOT */
         case ACTION_HALT:
         case ACTION_POWEROFF:
         case ACTION_REBOOT:
-- 
1.8.1



More information about the systemd-devel mailing list