Blob Blame History Raw
From 8b326c2f30c021bb4a873fd67608bd79609d4bbe Mon Sep 17 00:00:00 2001
From: "Lee, Chun-Yi" <jlee@suse.com>
Date: Tue, 7 Jul 2015 15:25:59 +0800
Subject: [PATCH v2 05/16] x86/efi: Get entropy through EFI random number
 generator protocol

Patch-mainline: Submitted, Tue, 11 Aug 2015 14:13:31 +0800 - linux-efi
References: fate#316350

To grab random numbers through EFI protocol as one of the entropies
source of swsusp key, this patch adds the logic for accessing EFI RNG
(random number generator) protocol that's introduced since UEFI 2.4.

Joey Lee:
 - Only keep the 64-bit parts because v3.12 kernel doesn't support
   32-bit UEFI.
 - Modfied the way to call EFI function by using efi_call_physx().

Signed-off-by: Lee, Chun-Yi <jlee@suse.com>
---
 arch/x86/boot/compressed/efi_random.c |  145 ++++++++++++++++++++++++++++++++++
 include/linux/efi.h                   |    8 +
 2 files changed, 153 insertions(+)

--- a/arch/x86/boot/compressed/efi_random.c
+++ b/arch/x86/boot/compressed/efi_random.c
@@ -1,7 +1,145 @@
 #include "misc.h"
 
 #include <linux/efi.h>
+#include <linux/stringify.h>
 #include <asm/archrandom.h>
+#include <asm/efi.h>
+
+#define EFI_STATUS_STR(_status) \
+case EFI_##_status: \
+	return "EFI_" __stringify(_status);
+
+static char *efi_status_to_str(efi_status_t status)
+{
+	switch (status) {
+	EFI_STATUS_STR(SUCCESS)
+	EFI_STATUS_STR(INVALID_PARAMETER)
+	EFI_STATUS_STR(OUT_OF_RESOURCES)
+	EFI_STATUS_STR(DEVICE_ERROR)
+	EFI_STATUS_STR(WRITE_PROTECTED)
+	EFI_STATUS_STR(SECURITY_VIOLATION)
+	EFI_STATUS_STR(NOT_FOUND)
+	}
+
+	return "";
+}
+
+static efi_status_t efi_locate_rng(efi_system_table_t *sys_table,
+				   void ***rng_handle)
+{
+	efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID;
+	unsigned long size = 0;
+	efi_status_t status;
+
+	*rng_handle = NULL;
+	status = efi_call_early(locate_handle,
+				EFI_LOCATE_BY_PROTOCOL,
+				&rng_proto, NULL, &size, *rng_handle);
+
+	if (status == EFI_BUFFER_TOO_SMALL) {
+		status = efi_call_early(allocate_pool,
+					EFI_LOADER_DATA,
+					size, (void **)rng_handle);
+
+		if (status != EFI_SUCCESS) {
+			efi_printk(sys_table, "Failed to alloc mem for rng_handle\n");
+			return status;
+		}
+
+		status = efi_call_early(locate_handle,
+					EFI_LOCATE_BY_PROTOCOL, &rng_proto,
+					NULL, &size, *rng_handle);
+	}
+
+	if (status != EFI_SUCCESS)
+		efi_call_early(free_pool, *rng_handle);
+
+	return status;
+}
+
+static bool efi_rng_supported64(efi_system_table_t *sys_table, void **rng_handle)
+{
+	const struct efi_config *efi_early = __efi_early();
+	efi_rng_protocol_64 *rng = NULL;
+	efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID;
+	u64 *handles = (u64 *)(unsigned long)rng_handle;
+	unsigned long size = 0;
+	void **algorithmlist = NULL;
+	efi_status_t status;
+
+	status = efi_call_early(handle_protocol, handles[0],
+				&rng_proto, (void **)&rng);
+	if (status != EFI_SUCCESS)
+		efi_printk(sys_table, "Failed to get EFI_RNG_PROTOCOL handles\n");
+
+	if (status == EFI_SUCCESS && rng) {
+		status = efi_early->call((unsigned long)rng->get_info, rng,
+					&size, algorithmlist);
+		return (status == EFI_BUFFER_TOO_SMALL);
+	}
+
+	return false;
+}
+
+static bool efi_rng_supported(efi_system_table_t *sys_table)
+{
+	bool supported;
+	efi_status_t status;
+	void **rng_handle = NULL;
+
+	status = efi_locate_rng(sys_table, &rng_handle);
+	if (status != EFI_SUCCESS)
+		return false;
+
+	supported = efi_rng_supported64(sys_table, rng_handle);
+	efi_call_early(free_pool, rng_handle);
+
+	return supported;
+}
+
+static unsigned long efi_get_rng64(efi_system_table_t *sys_table,
+				   void **rng_handle)
+{
+	const struct efi_config *efi_early = __efi_early();
+	efi_rng_protocol_64 *rng = NULL;
+	efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID;
+	u64 *handles = (u64 *)(unsigned long)rng_handle;
+	efi_status_t status;
+	unsigned long rng_number;
+
+	status = efi_call_early(handle_protocol, handles[0],
+				&rng_proto, (void **)&rng);
+	if (status != EFI_SUCCESS)
+		efi_printk(sys_table, "Failed to get EFI_RNG_PROTOCOL handles\n");
+
+	if (status == EFI_SUCCESS && rng) {
+		status = efi_early->call((unsigned long)rng->get_rng, rng, NULL,
+					sizeof(rng_number), &rng_number);
+		if (status != EFI_SUCCESS) {
+			efi_printk(sys_table, "Failed to get RNG value: ");
+			efi_printk(sys_table, efi_status_to_str(status));
+			efi_printk(sys_table, "\n");
+		}
+	}
+
+	return rng_number;
+}
+
+static unsigned long efi_get_rng(efi_system_table_t *sys_table)
+{
+	unsigned long random = 0;
+	efi_status_t status;
+	void **rng_handle = NULL;
+
+	status = efi_locate_rng(sys_table, &rng_handle);
+	if (status != EFI_SUCCESS)
+		return 0;
+
+	random = efi_get_rng64(sys_table, rng_handle);
+	efi_call_early(free_pool, rng_handle);
+
+	return random;
+}
 
 #define EDX_TSC		(1 << 4)
 #define ECX_RDRAND	(1 << 30)
@@ -46,6 +184,13 @@ static unsigned long get_random_long(uns
 		use_i8254 = false;
 	}
 
+	if (efi_rng_supported(sys_table)) {
+		raw = efi_get_rng(sys_table);
+		if (raw)
+			random ^= raw;
+		use_i8254 = false;
+	}
+
 	if (use_i8254)
 		random ^= read_i8254();
 
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -428,6 +428,11 @@ typedef struct {
 #define EFI_PCI_IO_ATTRIBUTE_VGA_PALETTE_IO_16 0x20000
 #define EFI_PCI_IO_ATTRIBUTE_VGA_IO_16 0x40000
 
+typedef struct {
+	u64 get_info;
+	u64 get_rng;
+} efi_rng_protocol_64;
+
 /*
  * Types and defines for EFI ResetSystem
  */
@@ -629,6 +634,9 @@ typedef struct {
 #define EFI_1_10_SYSTEM_TABLE_REVISION  ((1 << 16) | (10))
 #define EFI_1_02_SYSTEM_TABLE_REVISION  ((1 << 16) | (02))
 
+#define EFI_RNG_PROTOCOL_GUID \
+    EFI_GUID(  0x3152bca5, 0xeade, 0x433d, 0x86, 0x2e, 0xc0, 0x1c, 0xdc, 0x29, 0x1f, 0x44 )
+
 typedef struct {
 	efi_table_hdr_t hdr;
 	u64 fw_vendor;	/* physical addr of CHAR16 vendor string */