All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] efibc: EFI Bootloader Control
@ 2016-04-01 13:09 Compostella, Jeremy
       [not found] ` <87bn5tlbkw.fsf-e2kLcBBGnpIIbNWpFUBdLQzuBQEzu/OPQQ4Iyu8u01E@public.gmane.org>
  0 siblings, 1 reply; 7+ messages in thread
From: Compostella, Jeremy @ 2016-04-01 13:09 UTC (permalink / raw)
  To: Matt Fleming; +Cc: linux-efi-u79uwXL29TY76Z2rM5mHXA, Stanacar, Stefan

This module installs a reboot hook, such that if reboot() is invoked
with a string argument NNN, "NNN" is copied to the
"LoaderEntryOneShot" EFI variable, to be read by the bootloader. If
the string matches one of the boot labels defined in its
configuration, the bootloader will boot once to that label.  The
"LoaderEntryRebootReason" EFI variable is set with the reboot reason:
"reboot", "shutdown", "kernel_panic" or "watchdog".  The bootloader
reads this reboot reason and takes particular action according to its
policy.

Signed-off-by: Jeremy Compostella <jeremy.compostella-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
---
 arch/x86/Kconfig               |  16 ++++
 arch/x86/platform/efi/Makefile |   1 +
 arch/x86/platform/efi/efibc.c  | 164 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 181 insertions(+)
 create mode 100644 arch/x86/platform/efi/efibc.c

diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 2dc18605..cd4b6e4 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -432,6 +432,22 @@ config X86_EXTENDED_PLATFORM
 	  generic distribution kernel, say Y here - otherwise say N.
 endif
 
+config EFI_BOOTLOADER_CONTROL
+	tristate "EFI Bootloader Control"
+	depends on EFI_VARS
+	default n
+	---help---
+	  This module installs a reboot hook, such that if reboot() is
+	  invoked with a string argument NNN, "NNN" is copied to the
+	  "LoaderEntryOneShot" EFI variable, to be read by the
+	  bootloader. If the string matches one of the boot labels
+	  defined in its configuration, the bootloader will boot once
+	  to that label. The "LoaderEntryRebootReason" EFI variable is
+	  set with the reboot reason: "reboot", "shutdown",
+	  "kernel_panic" or "watchdog". The bootloader reads this
+	  reboot reason and takes particular action according to its
+	  policy.
+
 if X86_64
 config X86_EXTENDED_PLATFORM
 	bool "Support for extended (non-PC) x86 platforms"
diff --git a/arch/x86/platform/efi/Makefile b/arch/x86/platform/efi/Makefile
index 066619b..05cf769 100644
--- a/arch/x86/platform/efi/Makefile
+++ b/arch/x86/platform/efi/Makefile
@@ -4,3 +4,4 @@ obj-$(CONFIG_EFI) 		+= quirks.o efi.o efi_$(BITS).o efi_stub_$(BITS).o
 obj-$(CONFIG_ACPI_BGRT) += efi-bgrt.o
 obj-$(CONFIG_EARLY_PRINTK_EFI)	+= early_printk.o
 obj-$(CONFIG_EFI_MIXED)		+= efi_thunk_$(BITS).o
+obj-$(CONFIG_EFI_BOOTLOADER_CONTROL)	+= efibc.o
diff --git a/arch/x86/platform/efi/efibc.c b/arch/x86/platform/efi/efibc.c
new file mode 100644
index 0000000..8ca8b43
--- /dev/null
+++ b/arch/x86/platform/efi/efibc.c
@@ -0,0 +1,164 @@
+/*
+ * efibc: control EFI bootloaders which obey LoaderEntryOneShot var
+ * Copyright (c) 2013-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/efi.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/reboot.h>
+#include <linux/kexec.h>
+#include <linux/slab.h>
+#include <linux/nls.h>
+
+#define MODULE_NAME "efibc"
+
+static const efi_guid_t LOADER_GUID =
+	EFI_GUID(0x4a67b082, 0x0a4c, 0x41cf,
+		 0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f);
+
+static const char REBOOT_TARGET[] = "LoaderEntryOneShot";
+static const char REBOOT_REASON[] = "LoaderEntryRebootReason";
+
+static const char * const WDT_SOURCE_PREFIX[] = {
+	"Kernel Watchdog", "Watchdog", "softlockup", "Software Watchdog"
+};
+
+static void efibc_str_to_str16(const char *str, wchar_t *str16)
+{
+	size_t size = strlen(str) + 1;
+
+	utf8s_to_utf16s(str, size, UTF16_LITTLE_ENDIAN, str16,
+			size * sizeof(*str16) / sizeof(*str));
+	str16[size - 1] = '\0';
+}
+
+static struct efivar_entry *efibc_get_var_entry(const char *name)
+{
+	wchar_t name16[strlen(name) + 1];
+	struct efivar_entry *entry;
+
+	efibc_str_to_str16(name, name16);
+
+	efivar_entry_iter_begin();
+	entry = efivar_entry_find(name16, LOADER_GUID,
+				  &efivar_sysfs_list, false);
+	efivar_entry_iter_end();
+
+	return entry;
+}
+
+static void efibc_set_variable(const char *name, const char *value)
+{
+	u32 attributes = EFI_VARIABLE_NON_VOLATILE
+		| EFI_VARIABLE_BOOTSERVICE_ACCESS
+		| EFI_VARIABLE_RUNTIME_ACCESS;
+	wchar_t name16[strlen(name) + 1];
+	wchar_t value16[strlen(value) + 1];
+	int ret;
+
+	efibc_str_to_str16(name, name16);
+	efibc_str_to_str16(value, value16);
+
+	ret = efivar_entry_set_safe(name16, LOADER_GUID, attributes, true,
+				    sizeof(value16), value16);
+	if (ret)
+		pr_err(MODULE_NAME ": failed to set %s EFI variable: 0x%x\n",
+		       name, ret);
+}
+
+static int efibc_reboot_notifier_call(struct notifier_block *notifier,
+				      unsigned long what, void *data)
+{
+	char *reason = what == SYS_RESTART ? "reboot" : "shutdown";
+
+	efibc_set_variable(REBOOT_REASON, reason);
+
+	if (!data)
+		return NOTIFY_DONE;
+
+	efibc_set_variable(REBOOT_TARGET, (char *)data);
+
+	return NOTIFY_DONE;
+}
+
+static bool is_watchdog_source(const char *str)
+{
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(WDT_SOURCE_PREFIX); i++)
+		if (!strncmp(str, WDT_SOURCE_PREFIX[i],
+			     strlen(WDT_SOURCE_PREFIX[i])))
+			return true;
+
+	return false;
+}
+
+static int efibc_panic_notifier_call(struct notifier_block *notifier,
+				     unsigned long what, void *data)
+{
+	char *reason;
+
+	/* If the reboot reason has already been supplied, keep it. */
+	if (efibc_get_var_entry(REBOOT_REASON))
+		return NOTIFY_DONE;
+
+	if (data && is_watchdog_source((char *)data))
+		reason = "watchdog";
+	else
+		reason = "kernel_panic";
+
+	efibc_set_variable(REBOOT_REASON, reason);
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block efibc_reboot_notifier = {
+	.notifier_call = efibc_reboot_notifier_call,
+};
+
+static struct notifier_block efibc_panic_notifier = {
+	.notifier_call  = efibc_panic_notifier_call,
+};
+
+static int __init efibc_init(void)
+{
+	int ret;
+
+	if (!efi_enabled(EFI_RUNTIME_SERVICES))
+		return -ENODEV;
+
+	ret = register_reboot_notifier(&efibc_reboot_notifier);
+	if (ret) {
+		pr_err(MODULE_NAME ": unable to register reboot notifier\n");
+		return ret;
+	}
+
+	atomic_notifier_chain_register(&panic_notifier_list,
+				       &efibc_panic_notifier);
+
+	return 0;
+}
+module_init(efibc_init);
+
+static void __exit efibc_exit(void)
+{
+	unregister_reboot_notifier(&efibc_reboot_notifier);
+	atomic_notifier_chain_unregister(&panic_notifier_list,
+					 &efibc_panic_notifier);
+}
+module_exit(efibc_exit);
+
+MODULE_AUTHOR("Jeremy Compostella <jeremy.compostella-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>");
+MODULE_AUTHOR("Matt Gumbel <matthew.k.gumbel-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org");
+MODULE_DESCRIPTION("EFI Bootloader Control");
+MODULE_LICENSE("GPL v2");
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH] efibc: EFI Bootloader Control
       [not found] ` <87bn5tlbkw.fsf-e2kLcBBGnpIIbNWpFUBdLQzuBQEzu/OPQQ4Iyu8u01E@public.gmane.org>
@ 2016-04-14 16:00   ` Matt Fleming
       [not found]     ` <20160414160006.GR2829-mF/unelCI9GS6iBeEJttW/XRex20P6io@public.gmane.org>
  0 siblings, 1 reply; 7+ messages in thread
From: Matt Fleming @ 2016-04-14 16:00 UTC (permalink / raw)
  To: Compostella, Jeremy; +Cc: linux-efi-u79uwXL29TY76Z2rM5mHXA, Stanacar, Stefan

(Sorry for the delay)

On Fri, 01 Apr, at 03:09:51PM, Compostella, Jeremy wrote:
> This module installs a reboot hook, such that if reboot() is invoked
> with a string argument NNN, "NNN" is copied to the
> "LoaderEntryOneShot" EFI variable, to be read by the bootloader. If
> the string matches one of the boot labels defined in its
> configuration, the bootloader will boot once to that label.  The
> "LoaderEntryRebootReason" EFI variable is set with the reboot reason:
> "reboot", "shutdown", "kernel_panic" or "watchdog".  The bootloader
> reads this reboot reason and takes particular action according to its
> policy.
 
This commit message is missing some info. In particular, I'd like see
to which boot loaders support this protocol and why this feature
should be a kernel driver and cannot be done with the userspace EFI
variable API.

Yes we've already discussed these bits on the mailing list, but
recording it for posterity in the commit log is very worthwhile.

> Signed-off-by: Jeremy Compostella <jeremy.compostella-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
> ---
>  arch/x86/Kconfig               |  16 ++++
>  arch/x86/platform/efi/Makefile |   1 +
>  arch/x86/platform/efi/efibc.c  | 164 +++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 181 insertions(+)
>  create mode 100644 arch/x86/platform/efi/efibc.c
 
Hehe, I suspect I was a little unclear in my previous comments about
moving this out of drivers/firmware/efi.

arch/x86/platform/efi is not the right place for this kind of stuff
either, since it contains core x86 EFI support.

That directory is no longer named appropriately, as EFI should not be
considered a feature of specific platforms - it's the standard
firmware technology nowadays on x86. I've been meaning to rename it.

But let's ignore the issue of finding the right home for now. I've got
some reservations about the panic notifier that I've outlined below.

> diff --git a/arch/x86/platform/efi/Makefile b/arch/x86/platform/efi/Makefile
> index 066619b..05cf769 100644
> --- a/arch/x86/platform/efi/Makefile
> +++ b/arch/x86/platform/efi/Makefile
> @@ -4,3 +4,4 @@ obj-$(CONFIG_EFI) 		+= quirks.o efi.o efi_$(BITS).o efi_stub_$(BITS).o
>  obj-$(CONFIG_ACPI_BGRT) += efi-bgrt.o
>  obj-$(CONFIG_EARLY_PRINTK_EFI)	+= early_printk.o
>  obj-$(CONFIG_EFI_MIXED)		+= efi_thunk_$(BITS).o
> +obj-$(CONFIG_EFI_BOOTLOADER_CONTROL)	+= efibc.o
> diff --git a/arch/x86/platform/efi/efibc.c b/arch/x86/platform/efi/efibc.c
> new file mode 100644
> index 0000000..8ca8b43
> --- /dev/null
> +++ b/arch/x86/platform/efi/efibc.c
> @@ -0,0 +1,164 @@
> +/*
> + * efibc: control EFI bootloaders which obey LoaderEntryOneShot var
> + * Copyright (c) 2013-2016, Intel Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + */
> +
> +#include <linux/efi.h>
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/reboot.h>
> +#include <linux/kexec.h>
> +#include <linux/slab.h>
> +#include <linux/nls.h>
> +
> +#define MODULE_NAME "efibc"
> +
> +static const efi_guid_t LOADER_GUID =
> +	EFI_GUID(0x4a67b082, 0x0a4c, 0x41cf,
> +		 0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f);

Please move this guid to include/linux/efi.h.

> +
> +static const char REBOOT_TARGET[] = "LoaderEntryOneShot";
> +static const char REBOOT_REASON[] = "LoaderEntryRebootReason";
> +
> +static const char * const WDT_SOURCE_PREFIX[] = {
> +	"Kernel Watchdog", "Watchdog", "softlockup", "Software Watchdog"
> +};

Please use lowercase for names, unless you're using #define.
Additionally, because REBOOT_* are only used in one place you could
just use the strings directly instead of making them file-global.

> +static void efibc_str_to_str16(const char *str, wchar_t *str16)
> +{

s/wchar_t/efi_char16_t/

> +	size_t size = strlen(str) + 1;
> +
> +	utf8s_to_utf16s(str, size, UTF16_LITTLE_ENDIAN, str16,
> +			size * sizeof(*str16) / sizeof(*str));
> +	str16[size - 1] = '\0';

	for (i = 0; i < strlen(str); i++)
		str16[i] = str[i];

	str16[i] = '\0';

right? Are there some scenarios where utf8s_to_utf16s() works and the
above loop doesn't? We have to do this conversion in the efivarfs file
system too, so does that need updating?

> +static struct efivar_entry *efibc_get_var_entry(const char *name)
> +{
> +	wchar_t name16[strlen(name) + 1];
> +	struct efivar_entry *entry;
> +
> +	efibc_str_to_str16(name, name16);
> +
> +	efivar_entry_iter_begin();
> +	entry = efivar_entry_find(name16, LOADER_GUID,
> +				  &efivar_sysfs_list, false);

Hmm... there's an interesting consequence of using efivar_sysfs_list
here, and that is that if someone creates this variable manually via
the efivarfs file system you won't see it on efivar_sysfs_list.

Of course, when you restart the machine you will see it then, so it's
probably not a big deal for this driver that only cares about reboot;
I just wanted to make you aware.

> +	efivar_entry_iter_end();
> +
> +	return entry;
> +}
> +
> +static void efibc_set_variable(const char *name, const char *value)
> +{
> +	u32 attributes = EFI_VARIABLE_NON_VOLATILE
> +		| EFI_VARIABLE_BOOTSERVICE_ACCESS
> +		| EFI_VARIABLE_RUNTIME_ACCESS;
> +	wchar_t name16[strlen(name) + 1];
> +	wchar_t value16[strlen(value) + 1];
> +	int ret;

Please put the attribute value on a separate line from the
declaration.

> +
> +	efibc_str_to_str16(name, name16);
> +	efibc_str_to_str16(value, value16);
> +
> +	ret = efivar_entry_set_safe(name16, LOADER_GUID, attributes, true,
> +				    sizeof(value16), value16);

If it is always OK to block when calling SetVariable() you don't want
to be using efivar_entry_set_safe(). The regular efivar_entry_set()
will suffice.

The *_safe() version is special because it will handle being called in
a context where it is not allowed to block.

Oh but I just realised you call this from a panic handler, so it's not
safe to always block.

In which case, yes, using efivar_entry_set_safe() is correct but you
cannot always pass 'true' for the @block parameter.

> +	if (ret)
> +		pr_err(MODULE_NAME ": failed to set %s EFI variable: 0x%x\n",
> +		       name, ret);
> +}

You can redefine pr_fmt to avoid having to specify MODULE_NAME here,
e.g.

#define pr_fmt(fmt)	"efifbc: "

> +
> +static int efibc_reboot_notifier_call(struct notifier_block *notifier,
> +				      unsigned long what, void *data)
> +{
> +	char *reason = what == SYS_RESTART ? "reboot" : "shutdown";
> +

Let's rename 'what' to 'event' to provide an idea of the kind of data
it contains. This line needs breaking down too.

How about,

	const char *reason = "shutdown";

	if (event == SYS_RESTART)
		reason = "reboot";

Which makes it a little clearer that we treat the SYS_RESTART event as
being special and lump all other events under "shutdown".

> +}
> +
> +static bool is_watchdog_source(const char *str)
> +{
> +	size_t i;
> +
> +	for (i = 0; i < ARRAY_SIZE(WDT_SOURCE_PREFIX); i++)
> +		if (!strncmp(str, WDT_SOURCE_PREFIX[i],
> +			     strlen(WDT_SOURCE_PREFIX[i])))
> +			return true;
> +
> +	return false;
> +}
> +
> +static int efibc_panic_notifier_call(struct notifier_block *notifier,
> +				     unsigned long what, void *data)
> +{
> +	char *reason;
> +
> +	/* If the reboot reason has already been supplied, keep it. */
> +	if (efibc_get_var_entry(REBOOT_REASON))
> +		return NOTIFY_DONE;

I'm not sure this is the best idea. If, in the process of doing a
normal reboot, we panic, wouldn't the user rather see the panic
reason and not the reboot reason?

> +
> +	if (data && is_watchdog_source((char *)data))
> +		reason = "watchdog";
> +	else
> +		reason = "kernel_panic";
> +
> +	efibc_set_variable(REBOOT_REASON, reason);

Checking the panic string prefix for known watchdog strings is a bit
hacky. For example, this won't work with the hard lockup detector.
What happens if someone adds a new watchdog driver?

On second thought, I'm not at all sure of the value of this panic
notifier. As I understood it, the proposition of this driver was that
it allows the user to pass information via the kernel to the boot
loader upon reboot. This data is strictly opaque to the kernel, and
I'm OK with that.

However, with this panic handler, you're doing something different.
You're passing kernel strings directly (albeit translated to
"watchdog" or "kernel_panic") to the boot loader. You're creating an
ABI between the kernel and boot loaders.

Besides which, we already have code to store information in EFI
variables on panic; the efi-pstore driver.

Sorry, but NAK on this part of the patch.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] efibc: EFI Bootloader Control
       [not found]     ` <20160414160006.GR2829-mF/unelCI9GS6iBeEJttW/XRex20P6io@public.gmane.org>
@ 2016-04-18 13:04       ` Compostella, Jeremy
       [not found]         ` <87r3e383v7.fsf-e2kLcBBGnpIIbNWpFUBdLQzuBQEzu/OPQQ4Iyu8u01E@public.gmane.org>
  0 siblings, 1 reply; 7+ messages in thread
From: Compostella, Jeremy @ 2016-04-18 13:04 UTC (permalink / raw)
  To: Matt Fleming; +Cc: linux-efi-u79uwXL29TY76Z2rM5mHXA, Stanacar, Stefan

Matt Fleming <matt-mF/unelCI9GS6iBeEJttW/XRex20P6io@public.gmane.org> writes:

> (Sorry for the delay)
No worries.

> On Fri, 01 Apr, at 03:09:51PM, Compostella, Jeremy wrote:
>> This module installs a reboot hook, such that if reboot() is invoked
>> with a string argument NNN, "NNN" is copied to the
>> "LoaderEntryOneShot" EFI variable, to be read by the bootloader. If
>> the string matches one of the boot labels defined in its
>> configuration, the bootloader will boot once to that label.  The
>> "LoaderEntryRebootReason" EFI variable is set with the reboot reason:
>> "reboot", "shutdown", "kernel_panic" or "watchdog".  The bootloader
>> reads this reboot reason and takes particular action according to its
>> policy.
>  
> This commit message is missing some info. In particular, I'd like see
> to which boot loaders support this protocol and why this feature
> should be a kernel driver and cannot be done with the userspace EFI
> variable API.
>
> Yes we've already discussed these bits on the mailing list, but
> recording it for posterity in the commit log is very worthwhile.
Done.

>> Signed-off-by: Jeremy Compostella <jeremy.compostella-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
>> ---
>>  arch/x86/Kconfig               |  16 ++++
>>  arch/x86/platform/efi/Makefile |   1 +
>>  arch/x86/platform/efi/efibc.c  | 164 +++++++++++++++++++++++++++++++++++++++++
>>  3 files changed, 181 insertions(+)
>>  create mode 100644 arch/x86/platform/efi/efibc.c
>  
> Hehe, I suspect I was a little unclear in my previous comments about
> moving this out of drivers/firmware/efi.
>
> arch/x86/platform/efi is not the right place for this kind of stuff
> either, since it contains core x86 EFI support.
>
> That directory is no longer named appropriately, as EFI should not be
> considered a feature of specific platforms - it's the standard
> firmware technology nowadays on x86. I've been meaning to rename it.
I moved it to driver/firmware/efi/.

> But let's ignore the issue of finding the right home for now. I've got
> some reservations about the panic notifier that I've outlined below.
>
>> diff --git a/arch/x86/platform/efi/Makefile b/arch/x86/platform/efi/Makefile
>> index 066619b..05cf769 100644
>> --- a/arch/x86/platform/efi/Makefile
>> +++ b/arch/x86/platform/efi/Makefile
>> @@ -4,3 +4,4 @@ obj-$(CONFIG_EFI) 		+= quirks.o efi.o efi_$(BITS).o efi_stub_$(BITS).o
>>  obj-$(CONFIG_ACPI_BGRT) += efi-bgrt.o
>>  obj-$(CONFIG_EARLY_PRINTK_EFI)	+= early_printk.o
>>  obj-$(CONFIG_EFI_MIXED)		+= efi_thunk_$(BITS).o
>> +obj-$(CONFIG_EFI_BOOTLOADER_CONTROL)	+= efibc.o
>> diff --git a/arch/x86/platform/efi/efibc.c b/arch/x86/platform/efi/efibc.c
>> new file mode 100644
>> index 0000000..8ca8b43
>> --- /dev/null
>> +++ b/arch/x86/platform/efi/efibc.c
>> @@ -0,0 +1,164 @@
>> +/*
>> + * efibc: control EFI bootloaders which obey LoaderEntryOneShot var
>> + * Copyright (c) 2013-2016, Intel Corporation.
>> + *
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
>> + * more details.
>> + */
>> +
>> +#include <linux/efi.h>
>> +#include <linux/module.h>
>> +#include <linux/moduleparam.h>
>> +#include <linux/reboot.h>
>> +#include <linux/kexec.h>
>> +#include <linux/slab.h>
>> +#include <linux/nls.h>
>> +
>> +#define MODULE_NAME "efibc"
>> +
>> +static const efi_guid_t LOADER_GUID =
>> +	EFI_GUID(0x4a67b082, 0x0a4c, 0x41cf,
>> +		 0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f);
>
> Please move this guid to include/linux/efi.h.
Done.  But I'm not sure if EFI_LOADER_GUID constant name is
appropriate.  However, I'd like to avoid naming it after Kernelflinger
or Gummiboot.

>> +
>> +static const char REBOOT_TARGET[] = "LoaderEntryOneShot";
>> +static const char REBOOT_REASON[] = "LoaderEntryRebootReason";
>> +
>> +static const char * const WDT_SOURCE_PREFIX[] = {
>> +	"Kernel Watchdog", "Watchdog", "softlockup", "Software Watchdog"
>> +};
>
> Please use lowercase for names, unless you're using #define.
> Additionally, because REBOOT_* are only used in one place you could
> just use the strings directly instead of making them file-global.
Done.

>> +static void efibc_str_to_str16(const char *str, wchar_t *str16)
>> +{
>
> s/wchar_t/efi_char16_t/
>
>> +	size_t size = strlen(str) + 1;
>> +
>> +	utf8s_to_utf16s(str, size, UTF16_LITTLE_ENDIAN, str16,
>> +			size * sizeof(*str16) / sizeof(*str));
>> +	str16[size - 1] = '\0';
>
> 	for (i = 0; i < strlen(str); i++)
> 		str16[i] = str[i];
>
> 	str16[i] = '\0';
>
> right? Are there some scenarios where utf8s_to_utf16s() works and the
> above loop doesn't? We have to do this conversion in the efivarfs file
> system too, so does that need updating?
Done.  I do not see any scenario where ASCII is not enough.

>> +static struct efivar_entry *efibc_get_var_entry(const char *name)
>> +{
>> +	wchar_t name16[strlen(name) + 1];
>> +	struct efivar_entry *entry;
>> +
>> +	efibc_str_to_str16(name, name16);
>> +
>> +	efivar_entry_iter_begin();
>> +	entry = efivar_entry_find(name16, LOADER_GUID,
>> +				  &efivar_sysfs_list, false);
>
> Hmm... there's an interesting consequence of using efivar_sysfs_list
> here, and that is that if someone creates this variable manually via
> the efivarfs file system you won't see it on efivar_sysfs_list.
>
> Of course, when you restart the machine you will see it then, so it's
> probably not a big deal for this driver that only cares about reboot;
> I just wanted to make you aware.
I removed this function (not needed anymore).

>> +	efivar_entry_iter_end();
>> +
>> +	return entry;
>> +}
>> +
>> +static void efibc_set_variable(const char *name, const char *value)
>> +{
>> +	u32 attributes = EFI_VARIABLE_NON_VOLATILE
>> +		| EFI_VARIABLE_BOOTSERVICE_ACCESS
>> +		| EFI_VARIABLE_RUNTIME_ACCESS;
>> +	wchar_t name16[strlen(name) + 1];
>> +	wchar_t value16[strlen(value) + 1];
>> +	int ret;
>
> Please put the attribute value on a separate line from the
> declaration.
Done.

>> +
>> +	efibc_str_to_str16(name, name16);
>> +	efibc_str_to_str16(value, value16);
>> +
>> +	ret = efivar_entry_set_safe(name16, LOADER_GUID, attributes, true,
>> +				    sizeof(value16), value16);
>
> If it is always OK to block when calling SetVariable() you don't want
> to be using efivar_entry_set_safe(). The regular efivar_entry_set()
> will suffice.
>
> The *_safe() version is special because it will handle being called in
> a context where it is not allowed to block.


> Oh but I just realised you call this from a panic handler, so it's not
> safe to always block.
>
> In which case, yes, using efivar_entry_set_safe() is correct but you
> cannot always pass 'true' for the @block parameter.
>
>> +	if (ret)
>> +		pr_err(MODULE_NAME ": failed to set %s EFI variable: 0x%x\n",
>> +		       name, ret);
>> +}
>
> You can redefine pr_fmt to avoid having to specify MODULE_NAME here,
> e.g.
>
> #define pr_fmt(fmt)	"efifbc: "
Done.

>> +
>> +static int efibc_reboot_notifier_call(struct notifier_block *notifier,
>> +				      unsigned long what, void *data)
>> +{
>> +	char *reason = what == SYS_RESTART ? "reboot" : "shutdown";
>> +
>
> Let's rename 'what' to 'event' to provide an idea of the kind of data
> it contains. This line needs breaking down too.
>
> How about,
>
> 	const char *reason = "shutdown";
>
> 	if (event == SYS_RESTART)
> 		reason = "reboot";
>
> Which makes it a little clearer that we treat the SYS_RESTART event as
> being special and lump all other events under "shutdown".
Done.

>> +}
>> +
>> +static bool is_watchdog_source(const char *str)
>> +{
>> +	size_t i;
>> +
>> +	for (i = 0; i < ARRAY_SIZE(WDT_SOURCE_PREFIX); i++)
>> +		if (!strncmp(str, WDT_SOURCE_PREFIX[i],
>> +			     strlen(WDT_SOURCE_PREFIX[i])))
>> +			return true;
>> +
>> +	return false;
>> +}
>> +
>> +static int efibc_panic_notifier_call(struct notifier_block *notifier,
>> +				     unsigned long what, void *data)
>> +{
>> +	char *reason;
>> +
>> +	/* If the reboot reason has already been supplied, keep it. */
>> +	if (efibc_get_var_entry(REBOOT_REASON))
>> +		return NOTIFY_DONE;
>
> I'm not sure this is the best idea. If, in the process of doing a
> normal reboot, we panic, wouldn't the user rather see the panic
> reason and not the reboot reason?
Not needed anymore.

>> +
>> +	if (data && is_watchdog_source((char *)data))
>> +		reason = "watchdog";
>> +	else
>> +		reason = "kernel_panic";
>> +
>> +	efibc_set_variable(REBOOT_REASON, reason);
>
> Checking the panic string prefix for known watchdog strings is a bit
> hacky. For example, this won't work with the hard lockup detector.
> What happens if someone adds a new watchdog driver?
>
> On second thought, I'm not at all sure of the value of this panic
> notifier. As I understood it, the proposition of this driver was that
> it allows the user to pass information via the kernel to the boot
> loader upon reboot. This data is strictly opaque to the kernel, and
> I'm OK with that.
>
> However, with this panic handler, you're doing something different.
> You're passing kernel strings directly (albeit translated to
> "watchdog" or "kernel_panic") to the boot loader. You're creating an
> ABI between the kernel and boot loaders.
>
> Besides which, we already have code to store information in EFI
> variables on panic; the efi-pstore driver.
>
> Sorry, but NAK on this part of the patch.
Agreed.  I removed all the panic handler related code.  Though, I'd
like to keep the LoaderEntryRebootReason filled with "shutdown" or
"reboot" if that's okay with you.

Thanks,

Jérémy
-- 
One Emacs to rule them all

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] efibc: EFI Bootloader Control
       [not found]         ` <87r3e383v7.fsf-e2kLcBBGnpIIbNWpFUBdLQzuBQEzu/OPQQ4Iyu8u01E@public.gmane.org>
@ 2016-04-18 13:05           ` Compostella, Jeremy
       [not found]             ` <87mvor83tl.fsf-e2kLcBBGnpIIbNWpFUBdLQzuBQEzu/OPQQ4Iyu8u01E@public.gmane.org>
  2016-04-22 21:38           ` Matt Fleming
  1 sibling, 1 reply; 7+ messages in thread
From: Compostella, Jeremy @ 2016-04-18 13:05 UTC (permalink / raw)
  To: Matt Fleming; +Cc: linux-efi-u79uwXL29TY76Z2rM5mHXA, Stanacar, Stefan

This module installs a reboot hook, such that if reboot() is invoked
with a string argument NNN, "NNN" is copied to the
"LoaderEntryOneShot" EFI variable, to be read by the bootloader. If
the string matches one of the boot labels defined in its
configuration, the bootloader will boot once to that label.  The
"LoaderEntryRebootReason" EFI variable is set with the reboot reason:
"reboot", "shutdown".  The bootloader reads this reboot reason and
takes particular action according to its policy.

There are reboot implementations that do "reboot <reason>", such as
Android's reboot command and Upstart's reboot replacement, which pass
the reason as an argument to the reboot syscall.  There is no
platform-agnostic way how those could be modified to pass the reason
to the bootloader, regardless of platform or bootloader.

Signed-off-by: Jeremy Compostella <jeremy.compostella-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
---
 drivers/firmware/efi/Kconfig  |  15 +++++++
 drivers/firmware/efi/Makefile |   1 +
 drivers/firmware/efi/efibc.c  | 101 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/efi.h           |   4 ++
 4 files changed, 121 insertions(+)
 create mode 100644 drivers/firmware/efi/efibc.c

diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig
index e1670d5..0b0b635 100644
--- a/drivers/firmware/efi/Kconfig
+++ b/drivers/firmware/efi/Kconfig
@@ -87,6 +87,21 @@ config EFI_RUNTIME_WRAPPERS
 config EFI_ARMSTUB
 	bool
 
+config EFI_BOOTLOADER_CONTROL
+	tristate "EFI Bootloader Control"
+	depends on EFI_VARS
+	default n
+	---help---
+	  This module installs a reboot hook, such that if reboot() is
+	  invoked with a string argument NNN, "NNN" is copied to the
+	  "LoaderEntryOneShot" EFI variable, to be read by the
+	  bootloader. If the string matches one of the boot labels
+	  defined in its configuration, the bootloader will boot once
+	  to that label. The "LoaderEntryRebootReason" EFI variable is
+	  set with the reboot reason: "reboot" or "shutdown". The
+	  bootloader reads this reboot reason and takes particular
+	  action according to its policy.
+
 endmenu
 
 config UEFI_CPER
diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile
index 62e654f..c35e218 100644
--- a/drivers/firmware/efi/Makefile
+++ b/drivers/firmware/efi/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_EFI_RUNTIME_MAP)		+= runtime-map.o
 obj-$(CONFIG_EFI_RUNTIME_WRAPPERS)	+= runtime-wrappers.o
 obj-$(CONFIG_EFI_STUB)			+= libstub/
 obj-$(CONFIG_EFI_FAKE_MEMMAP)		+= fake_mem.o
+obj-$(CONFIG_EFI_BOOTLOADER_CONTROL)	+= efibc.o
 
 arm-obj-$(CONFIG_EFI)			:= arm-init.o arm-runtime.o
 obj-$(CONFIG_ARM)			+= $(arm-obj-y)
diff --git a/drivers/firmware/efi/efibc.c b/drivers/firmware/efi/efibc.c
new file mode 100644
index 0000000..9833ef5
--- /dev/null
+++ b/drivers/firmware/efi/efibc.c
@@ -0,0 +1,101 @@
+/*
+ * efibc: control EFI bootloaders which obey LoaderEntryOneShot var
+ * Copyright (c) 2013-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ */
+
+#define pr_fmt(fmt) "efibc: " fmt
+
+#include <linux/efi.h>
+#include <linux/module.h>
+#include <linux/reboot.h>
+
+static void efibc_str_to_str16(const char *str, efi_char16_t *str16)
+{
+	size_t i;
+
+	for (i = 0; i < strlen(str); i++)
+		str16[i] = str[i];
+
+	str16[i] = '\0';
+}
+
+static void efibc_set_variable(const char *name, const char *value)
+{
+	int ret;
+	efi_guid_t guid = EFI_LOADER_GUID;
+	struct efivar_entry entry;
+	size_t size = (strlen(value) + 1) * sizeof(efi_char16_t);
+
+	if (size > sizeof(entry.var.Data))
+		pr_err("value is too large");
+
+	efibc_str_to_str16(name, entry.var.VariableName);
+	efibc_str_to_str16(value, (efi_char16_t *)entry.var.Data);
+	memcpy(&entry.var.VendorGuid, &guid, sizeof(guid));
+
+	ret = efivar_entry_set(&entry,
+			       EFI_VARIABLE_NON_VOLATILE
+			       | EFI_VARIABLE_BOOTSERVICE_ACCESS
+			       | EFI_VARIABLE_RUNTIME_ACCESS,
+			       size, entry.var.Data, NULL);
+	if (ret)
+		pr_err("failed to set %s EFI variable: 0x%x\n",
+		       name, ret);
+}
+
+static int efibc_reboot_notifier_call(struct notifier_block *notifier,
+				      unsigned long event, void *data)
+{
+	char *reason = "shutdown";
+
+	if (event == SYS_RESTART)
+		reason = "reboot";
+
+	efibc_set_variable("LoaderEntryRebootReason", reason);
+
+	if (!data)
+		return NOTIFY_DONE;
+
+	efibc_set_variable("LoaderEntryOneShot", (char *)data);
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block efibc_reboot_notifier = {
+	.notifier_call = efibc_reboot_notifier_call,
+};
+
+static int __init efibc_init(void)
+{
+	int ret;
+
+	if (!efi_enabled(EFI_RUNTIME_SERVICES))
+		return -ENODEV;
+
+	ret = register_reboot_notifier(&efibc_reboot_notifier);
+	if (ret)
+		pr_err("unable to register reboot notifier\n");
+
+	return ret;
+}
+module_init(efibc_init);
+
+static void __exit efibc_exit(void)
+{
+	unregister_reboot_notifier(&efibc_reboot_notifier);
+}
+module_exit(efibc_exit);
+
+MODULE_AUTHOR("Jeremy Compostella <jeremy.compostella-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>");
+MODULE_AUTHOR("Matt Gumbel <matthew.k.gumbel-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org");
+MODULE_DESCRIPTION("EFI Bootloader Control");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 1626474..8dbf034 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -623,6 +623,10 @@ void efi_native_runtime_setup(void);
 	EFI_GUID(0x3152bca5, 0xeade, 0x433d, \
 		 0x86, 0x2e, 0xc0, 0x1c, 0xdc, 0x29, 0x1f, 0x44)
 
+#define EFI_LOADER_GUID \
+	EFI_GUID(0x4a67b082, 0x0a4c, 0x41cf, \
+		 0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f)
+
 typedef struct {
 	efi_guid_t guid;
 	u64 table;
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH] efibc: EFI Bootloader Control
       [not found]         ` <87r3e383v7.fsf-e2kLcBBGnpIIbNWpFUBdLQzuBQEzu/OPQQ4Iyu8u01E@public.gmane.org>
  2016-04-18 13:05           ` Compostella, Jeremy
@ 2016-04-22 21:38           ` Matt Fleming
  1 sibling, 0 replies; 7+ messages in thread
From: Matt Fleming @ 2016-04-22 21:38 UTC (permalink / raw)
  To: Compostella, Jeremy; +Cc: linux-efi-u79uwXL29TY76Z2rM5mHXA, Stanacar, Stefan

On Mon, 18 Apr, at 03:04:44PM, Compostella, Jeremy wrote:
> Done.  But I'm not sure if EFI_LOADER_GUID constant name is
> appropriate.  However, I'd like to avoid naming it after Kernelflinger
> or Gummiboot.

Yeah. What about EFI_LOADER_ENTRY_GUID ?

> Agreed.  I removed all the panic handler related code.  Though, I'd
> like to keep the LoaderEntryRebootReason filled with "shutdown" or
> "reboot" if that's okay with you.

Fine by me!

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] efibc: EFI Bootloader Control
       [not found]             ` <87mvor83tl.fsf-e2kLcBBGnpIIbNWpFUBdLQzuBQEzu/OPQQ4Iyu8u01E@public.gmane.org>
@ 2016-04-22 22:44               ` Matt Fleming
  2016-04-25 12:17               ` Matt Fleming
  1 sibling, 0 replies; 7+ messages in thread
From: Matt Fleming @ 2016-04-22 22:44 UTC (permalink / raw)
  To: Compostella, Jeremy; +Cc: linux-efi-u79uwXL29TY76Z2rM5mHXA, Stanacar, Stefan

On Mon, 18 Apr, at 03:05:42PM, Jeremy Compostella wrote:
> This module installs a reboot hook, such that if reboot() is invoked
> with a string argument NNN, "NNN" is copied to the
> "LoaderEntryOneShot" EFI variable, to be read by the bootloader. If
> the string matches one of the boot labels defined in its
> configuration, the bootloader will boot once to that label.  The
> "LoaderEntryRebootReason" EFI variable is set with the reboot reason:
> "reboot", "shutdown".  The bootloader reads this reboot reason and
> takes particular action according to its policy.
> 
> There are reboot implementations that do "reboot <reason>", such as
> Android's reboot command and Upstart's reboot replacement, which pass
> the reason as an argument to the reboot syscall.  There is no
> platform-agnostic way how those could be modified to pass the reason
> to the bootloader, regardless of platform or bootloader.
> 
> Signed-off-by: Jeremy Compostella <jeremy.compostella-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
> ---
>  drivers/firmware/efi/Kconfig  |  15 +++++++
>  drivers/firmware/efi/Makefile |   1 +
>  drivers/firmware/efi/efibc.c  | 101 ++++++++++++++++++++++++++++++++++++++++++
>  include/linux/efi.h           |   4 ++
>  4 files changed, 121 insertions(+)
>  create mode 100644 drivers/firmware/efi/efibc.c
 
It's difficult to have any concerns with a driver that's so small, so
I've applied this. Please note a couple of things I fixed up,

 - Add 'const' qualifier to char *reason
 - Change EFI_LOADER_GUID to LINUX_EFI_LOADER_ENTRY_GUID

Shout if you've issues with the change of name for the guid. It is
important to make it clear that this guid is not found in the UEFI
spec, which we've done in the past by prefixing guid names with LINUX_
(though presumably the Loader Entry GUID isn't necessarily Linux
specific).

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] efibc: EFI Bootloader Control
       [not found]             ` <87mvor83tl.fsf-e2kLcBBGnpIIbNWpFUBdLQzuBQEzu/OPQQ4Iyu8u01E@public.gmane.org>
  2016-04-22 22:44               ` Matt Fleming
@ 2016-04-25 12:17               ` Matt Fleming
  1 sibling, 0 replies; 7+ messages in thread
From: Matt Fleming @ 2016-04-25 12:17 UTC (permalink / raw)
  To: Compostella, Jeremy; +Cc: linux-efi-u79uwXL29TY76Z2rM5mHXA, Stanacar, Stefan

On Mon, 18 Apr, at 03:05:42PM, Jeremy Compostella wrote:
> +static void efibc_set_variable(const char *name, const char *value)
> +{
> +	int ret;
> +	efi_guid_t guid = EFI_LOADER_GUID;
> +	struct efivar_entry entry;
> +	size_t size = (strlen(value) + 1) * sizeof(efi_char16_t);

Putting 'entry' on the stack leads to the following build warning,

 drivers/firmware/efi/efibc.c: In function ‘efibc_set_variable’:
 drivers/firmware/efi/efibc.c:53:1: warning: the frame size of 2192 bytes is larger than 2048 bytes [-Wframe-larger-than=]

That's a big object.

Given that efibc_reboot_notifier_call() can only be invoked once,
could we just statically allocate 'entry' instead?

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2016-04-25 12:17 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-04-01 13:09 [PATCH] efibc: EFI Bootloader Control Compostella, Jeremy
     [not found] ` <87bn5tlbkw.fsf-e2kLcBBGnpIIbNWpFUBdLQzuBQEzu/OPQQ4Iyu8u01E@public.gmane.org>
2016-04-14 16:00   ` Matt Fleming
     [not found]     ` <20160414160006.GR2829-mF/unelCI9GS6iBeEJttW/XRex20P6io@public.gmane.org>
2016-04-18 13:04       ` Compostella, Jeremy
     [not found]         ` <87r3e383v7.fsf-e2kLcBBGnpIIbNWpFUBdLQzuBQEzu/OPQQ4Iyu8u01E@public.gmane.org>
2016-04-18 13:05           ` Compostella, Jeremy
     [not found]             ` <87mvor83tl.fsf-e2kLcBBGnpIIbNWpFUBdLQzuBQEzu/OPQQ4Iyu8u01E@public.gmane.org>
2016-04-22 22:44               ` Matt Fleming
2016-04-25 12:17               ` Matt Fleming
2016-04-22 21:38           ` Matt Fleming

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.