162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * efibc: control EFI bootloaders which obey LoaderEntryOneShot var
462306a36Sopenharmony_ci * Copyright (c) 2013-2016, Intel Corporation.
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#define pr_fmt(fmt) "efibc: " fmt
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/efi.h>
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <linux/reboot.h>
1262306a36Sopenharmony_ci#include <linux/slab.h>
1362306a36Sopenharmony_ci#include <linux/ucs2_string.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define MAX_DATA_LEN	512
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_cistatic int efibc_set_variable(efi_char16_t *name, efi_char16_t *value,
1862306a36Sopenharmony_ci			      unsigned long len)
1962306a36Sopenharmony_ci{
2062306a36Sopenharmony_ci	efi_status_t status;
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci	status = efi.set_variable(name, &LINUX_EFI_LOADER_ENTRY_GUID,
2362306a36Sopenharmony_ci				  EFI_VARIABLE_NON_VOLATILE
2462306a36Sopenharmony_ci				  | EFI_VARIABLE_BOOTSERVICE_ACCESS
2562306a36Sopenharmony_ci				  | EFI_VARIABLE_RUNTIME_ACCESS,
2662306a36Sopenharmony_ci				  len * sizeof(efi_char16_t), value);
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	if (status != EFI_SUCCESS) {
2962306a36Sopenharmony_ci		pr_err("failed to set EFI variable: 0x%lx\n", status);
3062306a36Sopenharmony_ci		return -EIO;
3162306a36Sopenharmony_ci	}
3262306a36Sopenharmony_ci	return 0;
3362306a36Sopenharmony_ci}
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic int efibc_reboot_notifier_call(struct notifier_block *notifier,
3662306a36Sopenharmony_ci				      unsigned long event, void *data)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci	efi_char16_t *reason = event == SYS_RESTART ? L"reboot"
3962306a36Sopenharmony_ci						    : L"shutdown";
4062306a36Sopenharmony_ci	const u8 *str = data;
4162306a36Sopenharmony_ci	efi_char16_t *wdata;
4262306a36Sopenharmony_ci	unsigned long l;
4362306a36Sopenharmony_ci	int ret;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	ret = efibc_set_variable(L"LoaderEntryRebootReason", reason,
4662306a36Sopenharmony_ci				 ucs2_strlen(reason));
4762306a36Sopenharmony_ci	if (ret || !data)
4862306a36Sopenharmony_ci		return NOTIFY_DONE;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	wdata = kmalloc(MAX_DATA_LEN * sizeof(efi_char16_t), GFP_KERNEL);
5162306a36Sopenharmony_ci	if (!wdata)
5262306a36Sopenharmony_ci		return NOTIFY_DONE;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	for (l = 0; l < MAX_DATA_LEN - 1 && str[l] != '\0'; l++)
5562306a36Sopenharmony_ci		wdata[l] = str[l];
5662306a36Sopenharmony_ci	wdata[l] = L'\0';
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	efibc_set_variable(L"LoaderEntryOneShot", wdata, l);
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	kfree(wdata);
6162306a36Sopenharmony_ci	return NOTIFY_DONE;
6262306a36Sopenharmony_ci}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistatic struct notifier_block efibc_reboot_notifier = {
6562306a36Sopenharmony_ci	.notifier_call = efibc_reboot_notifier_call,
6662306a36Sopenharmony_ci};
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic int __init efibc_init(void)
6962306a36Sopenharmony_ci{
7062306a36Sopenharmony_ci	int ret;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	if (!efi_rt_services_supported(EFI_RT_SUPPORTED_SET_VARIABLE))
7362306a36Sopenharmony_ci		return -ENODEV;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	ret = register_reboot_notifier(&efibc_reboot_notifier);
7662306a36Sopenharmony_ci	if (ret)
7762306a36Sopenharmony_ci		pr_err("unable to register reboot notifier\n");
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	return ret;
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_cimodule_init(efibc_init);
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic void __exit efibc_exit(void)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	unregister_reboot_notifier(&efibc_reboot_notifier);
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_cimodule_exit(efibc_exit);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ciMODULE_AUTHOR("Jeremy Compostella <jeremy.compostella@intel.com>");
9062306a36Sopenharmony_ciMODULE_AUTHOR("Matt Gumbel <matthew.k.gumbel@intel.com");
9162306a36Sopenharmony_ciMODULE_DESCRIPTION("EFI Bootloader Control");
9262306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
93