diff mbox

efi: add efi_test driver for exporting UEFI runtime service interfaces

Message ID 1468428313-30233-1-git-send-email-colin.king@canonical.com
State Rejected
Headers show

Commit Message

Colin Ian King July 13, 2016, 4:45 p.m. UTC
From: Ivan Hu <ivan.hu@canonical.com>

Adding driver to exports UEFI runtime service interfaces into userspace,
which allows to use and test UEFI runtime services provided by the
firmware.

This driver uses the efi.<service> function pointers directly instead of
going through the efivar API, because it is not trying to test the kernel
subsystem, just for testing the UEFI runtime service interfaces which are
provided by the firmware.

This driver is used by the Firmware Test Suite (FWTS) for testing the UEFI
runtime interfaces readiness of the firmware.
Details for FWTS are available from,
<https://wiki.ubuntu.com/FirmwareTestSuite>

Signed-off-by: Ivan Hu <ivan.hu@canonical.com>
---
 drivers/firmware/efi/Kconfig             |  15 +
 drivers/firmware/efi/Makefile            |   1 +
 drivers/firmware/efi/efi_test/Makefile   |   2 +
 drivers/firmware/efi/efi_test/efi_test.c | 718 +++++++++++++++++++++++++++++++
 drivers/firmware/efi/efi_test/efi_test.h | 110 +++++
 5 files changed, 846 insertions(+)
 create mode 100644 drivers/firmware/efi/efi_test/Makefile
 create mode 100644 drivers/firmware/efi/efi_test/efi_test.c
 create mode 100644 drivers/firmware/efi/efi_test/efi_test.h

Comments

Colin Ian King July 13, 2016, 4:45 p.m. UTC | #1
Ignore this, I sent the wrong patch. Apologies!


On 13/07/16 17:45, Colin King wrote:
> From: Ivan Hu <ivan.hu@canonical.com>
> 
> Adding driver to exports UEFI runtime service interfaces into userspace,
> which allows to use and test UEFI runtime services provided by the
> firmware.
> 
> This driver uses the efi.<service> function pointers directly instead of
> going through the efivar API, because it is not trying to test the kernel
> subsystem, just for testing the UEFI runtime service interfaces which are
> provided by the firmware.
> 
> This driver is used by the Firmware Test Suite (FWTS) for testing the UEFI
> runtime interfaces readiness of the firmware.
> Details for FWTS are available from,
> <https://wiki.ubuntu.com/FirmwareTestSuite>
> 
> Signed-off-by: Ivan Hu <ivan.hu@canonical.com>
> ---
>  drivers/firmware/efi/Kconfig             |  15 +
>  drivers/firmware/efi/Makefile            |   1 +
>  drivers/firmware/efi/efi_test/Makefile   |   2 +
>  drivers/firmware/efi/efi_test/efi_test.c | 718 +++++++++++++++++++++++++++++++
>  drivers/firmware/efi/efi_test/efi_test.h | 110 +++++
>  5 files changed, 846 insertions(+)
>  create mode 100644 drivers/firmware/efi/efi_test/Makefile
>  create mode 100644 drivers/firmware/efi/efi_test/efi_test.c
>  create mode 100644 drivers/firmware/efi/efi_test/efi_test.h
> 
> diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig
> index 6394152..bce0f15 100644
> --- a/drivers/firmware/efi/Kconfig
> +++ b/drivers/firmware/efi/Kconfig
> @@ -112,6 +112,21 @@ config EFI_CAPSULE_LOADER
>  
>  	  Most users should say N.
>  
> +config EFI_TEST
> +        tristate "EFI Runtime Services Support"
> +	depends on EFI
> +        default n
> +        help
> +          Say Y here to enable the runtime services support via /dev/efi_test.
> +          This driver uses the efi.<service> function pointers directly instead
> +          of going through the efivar API, because it is not trying to test the
> +          kernel subsystem, just for testing the UEFI runtime service
> +          interfaces which are provided by the firmware. This driver is using
> +          by the Firmware Test Suite (FWTS) for testing the UEFI runtime
> +          interfaces readiness of the firmware.
> +          Details for FWTS are available from:
> +	  <https://wiki.ubuntu.com/FirmwareTestSuite>
> +
>  endmenu
>  
>  config UEFI_CPER
> diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile
> index a219640..569d1a1 100644
> --- a/drivers/firmware/efi/Makefile
> +++ b/drivers/firmware/efi/Makefile
> @@ -20,6 +20,7 @@ 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
> +obj-$(CONFIG_EFI_TEST)			+= efi_test/
>  
>  arm-obj-$(CONFIG_EFI)			:= arm-init.o arm-runtime.o
>  obj-$(CONFIG_ARM)			+= $(arm-obj-y)
> diff --git a/drivers/firmware/efi/efi_test/Makefile b/drivers/firmware/efi/efi_test/Makefile
> new file mode 100644
> index 0000000..9b3e3d2
> --- /dev/null
> +++ b/drivers/firmware/efi/efi_test/Makefile
> @@ -0,0 +1,2 @@
> +obj-$(CONFIG_EFI_TEST)			+= efi_test.o
> +
> diff --git a/drivers/firmware/efi/efi_test/efi_test.c b/drivers/firmware/efi/efi_test/efi_test.c
> new file mode 100644
> index 0000000..3580e69
> --- /dev/null
> +++ b/drivers/firmware/efi/efi_test/efi_test.c
> @@ -0,0 +1,718 @@
> +/*
> + * EFI Test Driver for Runtime Services
> + *
> + * Copyright(C) 2012-2016 Canonical Ltd.
> + *
> + * This driver exports EFI runtime services interfaces into userspace, which
> + * allow to use and test UEFI runtime services provided by firmware.
> + *
> + */
> +
> +#include <linux/version.h>
> +#include <linux/miscdevice.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/proc_fs.h>
> +#include <linux/efi.h>
> +#include <linux/slab.h>
> +#include <linux/uaccess.h>
> +
> +#include "efi_test.h"
> +
> +MODULE_AUTHOR("Ivan Hu <ivan.hu@canonical.com>");
> +MODULE_DESCRIPTION("EFI Test Driver");
> +MODULE_LICENSE("GPL");
> +
> +/* commit 83e681897 turned efi_enabled into a function, so abstract it */
> +#ifdef EFI_RUNTIME_SERVICES
> +#define EFI_RUNTIME_ENABLED	efi_enabled(EFI_RUNTIME_SERVICES)
> +#else
> +#define EFI_RUNTIME_ENABLED	efi_enabled
> +#endif
> +
> +/*
> + * Count the bytes in 'str', including the terminating NULL.
> + *
> + * Note this function returns the number of *bytes*, not the number of
> + * ucs2 characters.
> + */
> +static inline size_t __ucs2_strsize(uint16_t  __user *str)
> +{
> +	uint16_t *s = str, c;
> +	size_t len;
> +
> +	if (!str)
> +		return 0;
> +
> +	/* Include terminating NULL */
> +	len = sizeof(uint16_t);
> +
> +	if (get_user(c, s++)) {
> +		WARN(1, "efi_test: Can't read userspace memory for size");
> +		return 0;
> +	}
> +
> +	while (c != 0) {
> +		if (get_user(c, s++)) {
> +			WARN(1, "efi_test: Can't read userspace memory for size");
> +			return 0;
> +		}
> +		len += sizeof(uint16_t);
> +	}
> +	return len;
> +}
> +
> +/*
> + * Free a buffer allocated by copy_ucs2_from_user_len()
> + */
> +static inline void ucs2_kfree(uint16_t *buf)
> +{
> +	kfree(buf);
> +}
> +
> +/*
> + * Allocate a buffer and copy a ucs2 string from user space into it.
> + */
> +static inline int
> +copy_ucs2_from_user_len(uint16_t **dst, uint16_t __user *src, size_t len)
> +{
> +	uint16_t *buf;
> +
> +	if (!src) {
> +		*dst = NULL;
> +		return 0;
> +	}
> +
> +	if (!access_ok(VERIFY_READ, src, 1))
> +		return -EFAULT;
> +
> +	buf = kmalloc(len, GFP_KERNEL);
> +	if (!buf) {
> +		*dst = 0;
> +		return -ENOMEM;
> +	}
> +	*dst = buf;
> +
> +	if (copy_from_user(*dst, src, len)) {
> +		kfree(buf);
> +		return -EFAULT;
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * Count the bytes in 'str', including the terminating NULL.
> + *
> + * Just a wrap for __ucs2_strsize
> + */
> +static inline int get_ucs2_strsize_from_user(uint16_t __user *src, size_t *len)
> +{
> +	if (!access_ok(VERIFY_READ, src, 1))
> +		return -EFAULT;
> +
> +	*len = __ucs2_strsize(src);
> +	if (*len == 0)
> +		return -EFAULT;
> +
> +	return 0;
> +}
> +
> +/*
> + * Calculate the required buffer allocation size and copy a ucs2 string
> + * from user space into it.
> + *
> + * This function differs from copy_ucs2_from_user_len() because it
> + * calculates the size of the buffer to allocate by taking the length of
> + * the string 'src'.
> + *
> + * If a non-zero value is returned, the caller MUST NOT access 'dst'.
> + *
> + * It is the caller's responsibility to free 'dst'.
> + */
> +static inline int copy_ucs2_from_user(uint16_t **dst, uint16_t __user *src)
> +{
> +	size_t len;
> +
> +	if (!access_ok(VERIFY_READ, src, 1))
> +		return -EFAULT;
> +
> +	len = __ucs2_strsize(src);
> +	return copy_ucs2_from_user_len(dst, src, len);
> +}
> +
> +/*
> + * Copy a ucs2 string to a user buffer.
> + *
> + * This function is a simple wrapper around copy_to_user() that does
> + * nothing if 'src' is NULL, which is useful for reducing the amount of
> + * NULL checking the caller has to do.
> + *
> + * 'len' specifies the number of bytes to copy.
> + */
> +static inline int
> +copy_ucs2_to_user_len(uint16_t __user *dst, uint16_t *src, size_t len)
> +{
> +	if (!src)
> +		return 0;
> +
> +	if (!access_ok(VERIFY_WRITE, dst, 1))
> +		return -EFAULT;
> +
> +	return copy_to_user(dst, src, len);
> +}
> +
> +static long efi_runtime_get_variable(unsigned long arg)
> +{
> +	struct efi_getvariable __user *getvariable;
> +	struct efi_getvariable getvariable_local;
> +	unsigned long datasize, prev_datasize, *dz;
> +	efi_guid_t vendor_guid, *vd = NULL;
> +	efi_status_t status;
> +	uint16_t *name = NULL;
> +	uint32_t attr, *at;
> +	void *data = NULL;
> +	int rv = 0;
> +
> +	getvariable = (struct efi_getvariable __user *)arg;
> +
> +	if (copy_from_user(&getvariable_local, getvariable,
> +			   sizeof(getvariable_local)))
> +		return -EFAULT;
> +	if (getvariable_local.data_size &&
> +	    get_user(datasize, getvariable_local.data_size))
> +		return -EFAULT;
> +	if (getvariable_local.vendor_guid) {
> +
> +		if (copy_from_user(&vendor_guid, getvariable_local.vendor_guid,
> +			   sizeof(vendor_guid)))
> +			return -EFAULT;
> +		vd = &vendor_guid;
> +	}
> +
> +	if (getvariable_local.variable_name) {
> +		rv = copy_ucs2_from_user(&name,
> +				getvariable_local.variable_name);
> +		if (rv)
> +			return rv;
> +	}
> +
> +	at = getvariable_local.attributes ? &attr : NULL;
> +	dz = getvariable_local.data_size ? &datasize : NULL;
> +
> +	if (getvariable_local.data_size && getvariable_local.data) {
> +		data = kmalloc(datasize, GFP_KERNEL);
> +		if (!data) {
> +			ucs2_kfree(name);
> +			return -ENOMEM;
> +		}
> +	}
> +
> +	prev_datasize = datasize;
> +	status = efi.get_variable(name, vd, at, dz, data);
> +	ucs2_kfree(name);
> +
> +	if (data) {
> +		if (status == EFI_SUCCESS && prev_datasize >= datasize)
> +			rv = copy_to_user(getvariable_local.data, data,
> +				datasize);
> +		kfree(data);
> +	}
> +
> +	if (rv)
> +		return rv;
> +
> +	if (put_user(status, getvariable_local.status))
> +		return -EFAULT;
> +	if (status == EFI_SUCCESS && prev_datasize >= datasize) {
> +		if (at && put_user(attr, getvariable_local.attributes))
> +			return -EFAULT;
> +		if (dz && put_user(datasize, getvariable_local.data_size))
> +			return -EFAULT;
> +		return 0;
> +	} else {
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static long efi_runtime_set_variable(unsigned long arg)
> +{
> +	struct efi_setvariable __user *setvariable;
> +	struct efi_setvariable setvariable_local;
> +	efi_guid_t vendor_guid;
> +	efi_status_t status;
> +	uint16_t *name;
> +	void *data;
> +	int rv;
> +
> +	setvariable = (struct efi_setvariable __user *)arg;
> +
> +	if (copy_from_user(&setvariable_local, setvariable,
> +			   sizeof(setvariable_local)))
> +		return -EFAULT;
> +	if (copy_from_user(&vendor_guid, setvariable_local.vendor_guid,
> +			   sizeof(vendor_guid)))
> +		return -EFAULT;
> +
> +	rv = copy_ucs2_from_user(&name, setvariable_local.variable_name);
> +	if (rv)
> +		return rv;
> +
> +	data = kmalloc(setvariable_local.data_size, GFP_KERNEL);
> +	if (!data) {
> +		ucs2_kfree(name);
> +		return -ENOMEM;
> +	}
> +	if (copy_from_user(data, setvariable_local.data,
> +			   setvariable_local.data_size)) {
> +		ucs2_kfree(data);
> +		kfree(name);
> +		return -EFAULT;
> +	}
> +
> +	status = efi.set_variable(name, &vendor_guid,
> +				setvariable_local.attributes,
> +				setvariable_local.data_size, data);
> +
> +	kfree(data);
> +	ucs2_kfree(name);
> +
> +	if (put_user(status, setvariable_local.status))
> +		return -EFAULT;
> +	return status == EFI_SUCCESS ? 0 : -EINVAL;
> +}
> +
> +static long efi_runtime_get_time(unsigned long arg)
> +{
> +	struct efi_gettime __user *gettime;
> +	struct efi_gettime  gettime_local;
> +	efi_status_t status;
> +	efi_time_cap_t cap;
> +	efi_time_t efi_time;
> +
> +	gettime = (struct efi_gettime __user *)arg;
> +	if (copy_from_user(&gettime_local, gettime, sizeof(gettime_local)))
> +		return -EFAULT;
> +
> +	status = efi.get_time(gettime_local.time ? &efi_time : NULL,
> +			      gettime_local.capabilities ? &cap : NULL);
> +
> +	if (put_user(status, gettime_local.status))
> +		return -EFAULT;
> +	if (status != EFI_SUCCESS) {
> +		pr_err("efi_test: can't read time\n");
> +		return -EINVAL;
> +	}
> +	if (gettime_local.capabilities) {
> +		efi_time_cap_t __user *cap_local;
> +
> +		cap_local = (efi_time_cap_t *)gettime_local.capabilities;
> +		if (put_user(cap.resolution,
> +			&(cap_local->resolution)) ||
> +			put_user(cap.accuracy, &(cap_local->accuracy)) ||
> +			put_user(cap.sets_to_zero, &(cap_local->sets_to_zero)))
> +			return -EFAULT;
> +	}
> +	if (gettime_local.time)
> +		return copy_to_user(gettime_local.time, &efi_time,
> +			sizeof(efi_time_t)) ? -EFAULT : 0;
> +	return 0;
> +}
> +
> +static long efi_runtime_set_time(unsigned long arg)
> +{
> +	struct efi_settime __user *settime;
> +	struct efi_settime settime_local;
> +	efi_status_t status;
> +	efi_time_t efi_time;
> +
> +	settime = (struct efi_settime __user *)arg;
> +	if (copy_from_user(&settime_local, settime, sizeof(settime_local)))
> +		return -EFAULT;
> +	if (copy_from_user(&efi_time, settime_local.time,
> +					sizeof(efi_time_t)))
> +		return -EFAULT;
> +	status = efi.set_time(&efi_time);
> +
> +	if (put_user(status, settime_local.status))
> +		return -EFAULT;
> +
> +	return status == EFI_SUCCESS ? 0 : -EINVAL;
> +}
> +
> +static long efi_runtime_get_waketime(unsigned long arg)
> +{
> +	struct efi_getwakeuptime __user *getwakeuptime;
> +	struct efi_getwakeuptime getwakeuptime_local;
> +	unsigned char enabled, pending;
> +	efi_status_t status;
> +	efi_time_t efi_time;
> +
> +	getwakeuptime = (struct efi_getwakeuptime __user *)arg;
> +	if (copy_from_user(&getwakeuptime_local, getwakeuptime,
> +				sizeof(getwakeuptime_local)))
> +		return -EFAULT;
> +
> +	status = efi.get_wakeup_time(
> +		getwakeuptime_local.enabled ? (efi_bool_t *)&enabled : NULL,
> +		getwakeuptime_local.pending ? (efi_bool_t *)&pending : NULL,
> +		getwakeuptime_local.time ? &efi_time : NULL);
> +
> +	if (put_user(status, getwakeuptime_local.status))
> +		return -EFAULT;
> +	if (status != EFI_SUCCESS)
> +		return -EINVAL;
> +	if (getwakeuptime_local.enabled && put_user(enabled,
> +						getwakeuptime_local.enabled))
> +		return -EFAULT;
> +
> +	if (getwakeuptime_local.time)
> +		return copy_to_user(getwakeuptime_local.time, &efi_time,
> +			sizeof(efi_time_t)) ? -EFAULT : 0;
> +	return 0;
> +}
> +
> +static long efi_runtime_set_waketime(unsigned long arg)
> +{
> +	struct efi_setwakeuptime __user *setwakeuptime;
> +	struct efi_setwakeuptime setwakeuptime_local;
> +	unsigned char enabled;
> +	efi_status_t status;
> +	efi_time_t efi_time;
> +
> +	setwakeuptime = (struct efi_setwakeuptime __user *)arg;
> +
> +	if (copy_from_user(&setwakeuptime_local, setwakeuptime,
> +				sizeof(setwakeuptime_local)))
> +		return -EFAULT;
> +
> +	enabled = setwakeuptime_local.enabled;
> +	if (setwakeuptime_local.time) {
> +		if (copy_from_user(&efi_time, setwakeuptime_local.time,
> +					sizeof(efi_time_t)))
> +			return -EFAULT;
> +
> +		status = efi.set_wakeup_time(enabled, &efi_time);
> +	} else {
> +		status = efi.set_wakeup_time(enabled, NULL);
> +	}
> +
> +	if (put_user(status, setwakeuptime_local.status))
> +		return -EFAULT;
> +
> +	return status == EFI_SUCCESS ? 0 : -EINVAL;
> +}
> +
> +static long efi_runtime_get_nextvariablename(unsigned long arg)
> +{
> +	struct efi_getnextvariablename __user *getnextvariablename;
> +	struct efi_getnextvariablename getnextvariablename_local;
> +	unsigned long name_size, prev_name_size = 0, *ns = NULL;
> +	efi_status_t status;
> +	efi_guid_t *vd = NULL;
> +	efi_guid_t vendor_guid;
> +	uint16_t *name = NULL;
> +	int rv;
> +
> +	getnextvariablename = (struct efi_getnextvariablename
> +							__user *)arg;
> +
> +	if (copy_from_user(&getnextvariablename_local, getnextvariablename,
> +			   sizeof(getnextvariablename_local)))
> +		return -EFAULT;
> +
> +	if (getnextvariablename_local.variable_name_size) {
> +		if (get_user(name_size,
> +				getnextvariablename_local.variable_name_size))
> +			return -EFAULT;
> +		ns = &name_size;
> +		prev_name_size = name_size;
> +	}
> +
> +	if (getnextvariablename_local.vendor_guid) {
> +		if (copy_from_user(&vendor_guid,
> +				getnextvariablename_local.vendor_guid,
> +				sizeof(vendor_guid)))
> +			return -EFAULT;
> +		vd = &vendor_guid;
> +	}
> +
> +	if (getnextvariablename_local.variable_name) {
> +		size_t name_string_size = 0;
> +
> +		rv = get_ucs2_strsize_from_user(
> +				getnextvariablename_local.variable_name,
> +				&name_string_size);
> +		if (rv)
> +			return rv;
> +		/*
> +		 * name_size may be smaller than the real buffer size where
> +		 * VariableName located in some use cases. The most typical
> +		 * case is passing a 0 toget the required buffer size for the
> +		 * 1st time call. So we need to copy the content from user
> +		 * space for at least the string size ofVariableName, or else
> +		 * the name passed to UEFI may not be terminatedas we expected.
> +		 */
> +		rv = copy_ucs2_from_user_len(&name,
> +				getnextvariablename_local.variable_name,
> +				prev_name_size > name_string_size ?
> +				prev_name_size : name_string_size);
> +		if (rv)
> +			return rv;
> +	}
> +
> +	status = efi.get_next_variable(ns, name, vd);
> +
> +	if (name) {
> +		rv = copy_ucs2_to_user_len(
> +				getnextvariablename_local.variable_name,
> +				name, prev_name_size);
> +		ucs2_kfree(name);
> +		if (rv)
> +			return -EFAULT;
> +	}
> +
> +	if (put_user(status, getnextvariablename_local.status))
> +		return -EFAULT;
> +
> +	if (ns) {
> +		if (put_user(*ns,
> +			getnextvariablename_local.variable_name_size))
> +			return -EFAULT;
> +	}
> +
> +	if (vd) {
> +		if (copy_to_user(getnextvariablename_local.vendor_guid,
> +				 vd, sizeof(efi_guid_t)))
> +			return -EFAULT;
> +	}
> +
> +	if (status != EFI_SUCCESS)
> +		return -EINVAL;
> +	return 0;
> +}
> +
> +static long efi_runtime_get_nexthighmonocount(unsigned long arg)
> +{
> +	struct efi_getnexthighmonotoniccount __user *getnexthighmonotoniccount;
> +	struct efi_getnexthighmonotoniccount getnexthighmonotoniccount_local;
> +	efi_status_t status;
> +	uint32_t count;
> +
> +	getnexthighmonotoniccount = (struct
> +			efi_getnexthighmonotoniccount __user *)arg;
> +
> +	if (copy_from_user(&getnexthighmonotoniccount_local,
> +			   getnexthighmonotoniccount,
> +			   sizeof(getnexthighmonotoniccount_local)))
> +		return -EFAULT;
> +
> +	status = efi.get_next_high_mono_count(
> +		getnexthighmonotoniccount_local.high_count ? &count : NULL);
> +
> +	if (put_user(status, getnexthighmonotoniccount_local.status))
> +		return -EFAULT;
> +
> +	if (getnexthighmonotoniccount_local.high_count &&
> +	    put_user(count, getnexthighmonotoniccount_local.high_count))
> +		return -EFAULT;
> +
> +	if (status != EFI_SUCCESS)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +
> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 1, 0)
> +static long efi_runtime_query_variableinfo(unsigned long arg)
> +{
> +	struct efi_queryvariableinfo __user *queryvariableinfo;
> +	struct efi_queryvariableinfo queryvariableinfo_local;
> +	efi_status_t status;
> +	uint64_t max_storage, remaining, max_size;
> +
> +	queryvariableinfo = (struct efi_queryvariableinfo __user *)arg;
> +
> +	if (copy_from_user(&queryvariableinfo_local, queryvariableinfo,
> +			   sizeof(queryvariableinfo_local)))
> +		return -EFAULT;
> +
> +	status = efi.query_variable_info(queryvariableinfo_local.attributes,
> +					 &max_storage, &remaining, &max_size);
> +
> +	if (put_user(max_storage,
> +		     queryvariableinfo_local.maximum_variable_storage_size))
> +		return -EFAULT;
> +
> +	if (put_user(remaining,
> +		     queryvariableinfo_local.remaining_variable_storage_size))
> +		return -EFAULT;
> +
> +	if (put_user(max_size, queryvariableinfo_local.maximum_variable_size))
> +		return -EFAULT;
> +
> +	if (put_user(status, queryvariableinfo_local.status))
> +		return -EFAULT;
> +	if (status != EFI_SUCCESS)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +static long efi_runtime_query_capsulecaps(unsigned long arg)
> +{
> +	struct efi_querycapsulecapabilities __user *u_caps;
> +	struct efi_querycapsulecapabilities caps;
> +	efi_capsule_header_t *capsules;
> +	efi_status_t status;
> +	uint64_t max_size;
> +	int i, reset_type;
> +
> +	u_caps = (struct efi_querycapsulecapabilities __user *)arg;
> +
> +	if (copy_from_user(&caps, u_caps, sizeof(caps)))
> +		return -EFAULT;
> +
> +	capsules = kcalloc(caps.capsule_count + 1,
> +			   sizeof(efi_capsule_header_t), GFP_KERNEL);
> +	if (!capsules)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < caps.capsule_count; i++) {
> +		efi_capsule_header_t *c;
> +		/*
> +		 * We cannot dereference caps.CapsuleHeaderArray directly to
> +		 * obtain the address of the capsule as it resides in the
> +		 * user space
> +		 */
> +		if (get_user(c, caps.capsule_header_array + i))
> +			return -EFAULT;
> +		if (copy_from_user(&capsules[i], c,
> +				sizeof(efi_capsule_header_t)))
> +			return -EFAULT;
> +	}
> +
> +	caps.capsule_header_array = &capsules;
> +
> +	status = efi.query_capsule_caps((efi_capsule_header_t **)
> +					caps.capsule_header_array,
> +					caps.capsule_count,
> +					&max_size, &reset_type);
> +
> +	if (put_user(status, caps.status))
> +		return -EFAULT;
> +
> +	if (put_user(max_size, caps.maximum_capsule_size))
> +		return -EFAULT;
> +
> +	if (put_user(reset_type, caps.reset_type))
> +		return -EFAULT;
> +
> +	if (status != EFI_SUCCESS)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +#endif
> +
> +static long efi_test_ioctl(struct file *file, unsigned int cmd,
> +							unsigned long arg)
> +{
> +	switch (cmd) {
> +	case EFI_RUNTIME_GET_VARIABLE:
> +		return efi_runtime_get_variable(arg);
> +
> +	case EFI_RUNTIME_SET_VARIABLE:
> +		return efi_runtime_set_variable(arg);
> +
> +	case EFI_RUNTIME_GET_TIME:
> +		return efi_runtime_get_time(arg);
> +
> +	case EFI_RUNTIME_SET_TIME:
> +		return efi_runtime_set_time(arg);
> +
> +	case EFI_RUNTIME_GET_WAKETIME:
> +		return efi_runtime_get_waketime(arg);
> +
> +	case EFI_RUNTIME_SET_WAKETIME:
> +		return efi_runtime_set_waketime(arg);
> +
> +	case EFI_RUNTIME_GET_NEXTVARIABLENAME:
> +		return efi_runtime_get_nextvariablename(arg);
> +
> +	case EFI_RUNTIME_GET_NEXTHIGHMONOTONICCOUNT:
> +		return efi_runtime_get_nexthighmonocount(arg);
> +
> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 1, 0)
> +	case EFI_RUNTIME_QUERY_VARIABLEINFO:
> +		return efi_runtime_query_variableinfo(arg);
> +
> +	case EFI_RUNTIME_QUERY_CAPSULECAPABILITIES:
> +		return efi_runtime_query_capsulecaps(arg);
> +#endif
> +	}
> +
> +	return -ENOTTY;
> +}
> +
> +static int efi_test_open(struct inode *inode, struct file *file)
> +{
> +	/*
> +	 * nothing special to do here
> +	 * We do accept multiple open files at the same time as we
> +	 * synchronize on the per call operation.
> +	 */
> +	return 0;
> +}
> +
> +static int efi_test_close(struct inode *inode, struct file *file)
> +{
> +	return 0;
> +}
> +
> +/*
> + *	The various file operations we support.
> + */
> +static const struct file_operations efi_test_fops = {
> +	.owner		= THIS_MODULE,
> +	.unlocked_ioctl	= efi_test_ioctl,
> +	.open		= efi_test_open,
> +	.release	= efi_test_close,
> +	.llseek		= no_llseek,
> +};
> +
> +static struct miscdevice efi_test_dev = {
> +	MISC_DYNAMIC_MINOR,
> +	"efi_test",
> +	&efi_test_fops
> +};
> +
> +static int __init efi_test_init(void)
> +{
> +	int ret;
> +
> +	if (!EFI_RUNTIME_ENABLED) {
> +		pr_err("efi_test: EFI runtime services not enabled.\n");
> +		return -ENODEV;
> +	}
> +
> +	ret = misc_register(&efi_test_dev);
> +	if (ret) {
> +		pr_err("efi_test: can't misc_register on minor=%d\n",
> +			MISC_DYNAMIC_MINOR);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void __exit efi_test_exit(void)
> +{
> +	misc_deregister(&efi_test_dev);
> +}
> +
> +module_init(efi_test_init);
> +module_exit(efi_test_exit);
> diff --git a/drivers/firmware/efi/efi_test/efi_test.h b/drivers/firmware/efi/efi_test/efi_test.h
> new file mode 100644
> index 0000000..648030f
> --- /dev/null
> +++ b/drivers/firmware/efi/efi_test/efi_test.h
> @@ -0,0 +1,110 @@
> +/*
> + * EFI Test driver Header
> + *
> + * Copyright(C) 2012-2016 Canonical Ltd.
> + *
> + */
> +
> +#ifndef _DRIVERS_FIRMWARE_EFI_TEST_H_
> +#define _DRIVERS_FIRMWARE_EFI_TEST_H_
> +
> +#include <linux/efi.h>
> +
> +struct efi_getvariable {
> +	uint16_t	*variable_name;
> +	efi_guid_t	*vendor_guid;
> +	uint32_t	*attributes;
> +	uint64_t	*data_size;
> +	void		*data;
> +	uint64_t	*status;
> +} __packed;
> +
> +struct efi_setvariable {
> +	uint16_t	*variable_name;
> +	efi_guid_t	*vendor_guid;
> +	uint32_t	attributes;
> +	uint64_t	data_size;
> +	void		*data;
> +	uint64_t	*status;
> +} __packed;
> +
> +struct efi_getnextvariablename {
> +	uint64_t	*variable_name_size;
> +	uint16_t	*variable_name;
> +	efi_guid_t	*vendor_guid;
> +	uint64_t	*status;
> +} __packed;
> +
> +struct efi_queryvariableinfo {
> +	uint32_t	attributes;
> +	uint64_t	*maximum_variable_storage_size;
> +	uint64_t	*remaining_variable_storage_size;
> +	uint64_t	*maximum_variable_size;
> +	uint64_t	*status;
> +} __packed;
> +
> +struct efi_gettime {
> +	efi_time_t		*time;
> +	efi_time_cap_t		*capabilities;
> +	uint64_t		*status;
> +} __packed;
> +
> +struct efi_settime {
> +	efi_time_t		*time;
> +	uint64_t		*status;
> +} __packed;
> +
> +struct efi_getwakeuptime {
> +	uint8_t		*enabled;
> +	uint8_t		*pending;
> +	efi_time_t	*time;
> +	uint64_t	*status;
> +} __packed;
> +
> +struct efi_setwakeuptime {
> +	uint8_t		enabled;
> +	efi_time_t	*time;
> +	uint64_t	*status;
> +} __packed;
> +
> +struct efi_getnexthighmonotoniccount {
> +	uint32_t	*high_count;
> +	uint64_t	*status;
> +} __packed;
> +
> +struct efi_querycapsulecapabilities {
> +	efi_capsule_header_t	**capsule_header_array;
> +	uint64_t		capsule_count;
> +	uint64_t		*maximum_capsule_size;
> +	int			*reset_type;
> +	uint64_t		*status;
> +} __packed;
> +
> +#define EFI_RUNTIME_GET_VARIABLE \
> +	_IOWR('p', 0x01, struct efi_getvariable)
> +#define EFI_RUNTIME_SET_VARIABLE \
> +	_IOW('p', 0x02, struct efi_setvariable)
> +
> +#define EFI_RUNTIME_GET_TIME \
> +	_IOR('p', 0x03, struct efi_gettime)
> +#define EFI_RUNTIME_SET_TIME \
> +	_IOW('p', 0x04, struct efi_settime)
> +
> +#define EFI_RUNTIME_GET_WAKETIME \
> +	_IOR('p', 0x05, struct efi_getwakeuptime)
> +#define EFI_RUNTIME_SET_WAKETIME \
> +	_IOW('p', 0x06, struct efi_setwakeuptime)
> +
> +#define EFI_RUNTIME_GET_NEXTVARIABLENAME \
> +	_IOWR('p', 0x07, struct efi_getnextvariablename)
> +
> +#define EFI_RUNTIME_QUERY_VARIABLEINFO \
> +	_IOR('p', 0x08, struct efi_queryvariableinfo)
> +
> +#define EFI_RUNTIME_GET_NEXTHIGHMONOTONICCOUNT \
> +	_IOR('p', 0x09, struct efi_getnexthighmonotoniccount)
> +
> +#define EFI_RUNTIME_QUERY_CAPSULECAPABILITIES \
> +	_IOR('p', 0x0A, struct efi_querycapsulecapabilities)
> +
> +#endif /* _DRIVERS_FIRMWARE_EFI_TEST_H_ */
>
diff mbox

Patch

diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig
index 6394152..bce0f15 100644
--- a/drivers/firmware/efi/Kconfig
+++ b/drivers/firmware/efi/Kconfig
@@ -112,6 +112,21 @@  config EFI_CAPSULE_LOADER
 
 	  Most users should say N.
 
+config EFI_TEST
+        tristate "EFI Runtime Services Support"
+	depends on EFI
+        default n
+        help
+          Say Y here to enable the runtime services support via /dev/efi_test.
+          This driver uses the efi.<service> function pointers directly instead
+          of going through the efivar API, because it is not trying to test the
+          kernel subsystem, just for testing the UEFI runtime service
+          interfaces which are provided by the firmware. This driver is using
+          by the Firmware Test Suite (FWTS) for testing the UEFI runtime
+          interfaces readiness of the firmware.
+          Details for FWTS are available from:
+	  <https://wiki.ubuntu.com/FirmwareTestSuite>
+
 endmenu
 
 config UEFI_CPER
diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile
index a219640..569d1a1 100644
--- a/drivers/firmware/efi/Makefile
+++ b/drivers/firmware/efi/Makefile
@@ -20,6 +20,7 @@  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
+obj-$(CONFIG_EFI_TEST)			+= efi_test/
 
 arm-obj-$(CONFIG_EFI)			:= arm-init.o arm-runtime.o
 obj-$(CONFIG_ARM)			+= $(arm-obj-y)
diff --git a/drivers/firmware/efi/efi_test/Makefile b/drivers/firmware/efi/efi_test/Makefile
new file mode 100644
index 0000000..9b3e3d2
--- /dev/null
+++ b/drivers/firmware/efi/efi_test/Makefile
@@ -0,0 +1,2 @@ 
+obj-$(CONFIG_EFI_TEST)			+= efi_test.o
+
diff --git a/drivers/firmware/efi/efi_test/efi_test.c b/drivers/firmware/efi/efi_test/efi_test.c
new file mode 100644
index 0000000..3580e69
--- /dev/null
+++ b/drivers/firmware/efi/efi_test/efi_test.c
@@ -0,0 +1,718 @@ 
+/*
+ * EFI Test Driver for Runtime Services
+ *
+ * Copyright(C) 2012-2016 Canonical Ltd.
+ *
+ * This driver exports EFI runtime services interfaces into userspace, which
+ * allow to use and test UEFI runtime services provided by firmware.
+ *
+ */
+
+#include <linux/version.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/efi.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include "efi_test.h"
+
+MODULE_AUTHOR("Ivan Hu <ivan.hu@canonical.com>");
+MODULE_DESCRIPTION("EFI Test Driver");
+MODULE_LICENSE("GPL");
+
+/* commit 83e681897 turned efi_enabled into a function, so abstract it */
+#ifdef EFI_RUNTIME_SERVICES
+#define EFI_RUNTIME_ENABLED	efi_enabled(EFI_RUNTIME_SERVICES)
+#else
+#define EFI_RUNTIME_ENABLED	efi_enabled
+#endif
+
+/*
+ * Count the bytes in 'str', including the terminating NULL.
+ *
+ * Note this function returns the number of *bytes*, not the number of
+ * ucs2 characters.
+ */
+static inline size_t __ucs2_strsize(uint16_t  __user *str)
+{
+	uint16_t *s = str, c;
+	size_t len;
+
+	if (!str)
+		return 0;
+
+	/* Include terminating NULL */
+	len = sizeof(uint16_t);
+
+	if (get_user(c, s++)) {
+		WARN(1, "efi_test: Can't read userspace memory for size");
+		return 0;
+	}
+
+	while (c != 0) {
+		if (get_user(c, s++)) {
+			WARN(1, "efi_test: Can't read userspace memory for size");
+			return 0;
+		}
+		len += sizeof(uint16_t);
+	}
+	return len;
+}
+
+/*
+ * Free a buffer allocated by copy_ucs2_from_user_len()
+ */
+static inline void ucs2_kfree(uint16_t *buf)
+{
+	kfree(buf);
+}
+
+/*
+ * Allocate a buffer and copy a ucs2 string from user space into it.
+ */
+static inline int
+copy_ucs2_from_user_len(uint16_t **dst, uint16_t __user *src, size_t len)
+{
+	uint16_t *buf;
+
+	if (!src) {
+		*dst = NULL;
+		return 0;
+	}
+
+	if (!access_ok(VERIFY_READ, src, 1))
+		return -EFAULT;
+
+	buf = kmalloc(len, GFP_KERNEL);
+	if (!buf) {
+		*dst = 0;
+		return -ENOMEM;
+	}
+	*dst = buf;
+
+	if (copy_from_user(*dst, src, len)) {
+		kfree(buf);
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+/*
+ * Count the bytes in 'str', including the terminating NULL.
+ *
+ * Just a wrap for __ucs2_strsize
+ */
+static inline int get_ucs2_strsize_from_user(uint16_t __user *src, size_t *len)
+{
+	if (!access_ok(VERIFY_READ, src, 1))
+		return -EFAULT;
+
+	*len = __ucs2_strsize(src);
+	if (*len == 0)
+		return -EFAULT;
+
+	return 0;
+}
+
+/*
+ * Calculate the required buffer allocation size and copy a ucs2 string
+ * from user space into it.
+ *
+ * This function differs from copy_ucs2_from_user_len() because it
+ * calculates the size of the buffer to allocate by taking the length of
+ * the string 'src'.
+ *
+ * If a non-zero value is returned, the caller MUST NOT access 'dst'.
+ *
+ * It is the caller's responsibility to free 'dst'.
+ */
+static inline int copy_ucs2_from_user(uint16_t **dst, uint16_t __user *src)
+{
+	size_t len;
+
+	if (!access_ok(VERIFY_READ, src, 1))
+		return -EFAULT;
+
+	len = __ucs2_strsize(src);
+	return copy_ucs2_from_user_len(dst, src, len);
+}
+
+/*
+ * Copy a ucs2 string to a user buffer.
+ *
+ * This function is a simple wrapper around copy_to_user() that does
+ * nothing if 'src' is NULL, which is useful for reducing the amount of
+ * NULL checking the caller has to do.
+ *
+ * 'len' specifies the number of bytes to copy.
+ */
+static inline int
+copy_ucs2_to_user_len(uint16_t __user *dst, uint16_t *src, size_t len)
+{
+	if (!src)
+		return 0;
+
+	if (!access_ok(VERIFY_WRITE, dst, 1))
+		return -EFAULT;
+
+	return copy_to_user(dst, src, len);
+}
+
+static long efi_runtime_get_variable(unsigned long arg)
+{
+	struct efi_getvariable __user *getvariable;
+	struct efi_getvariable getvariable_local;
+	unsigned long datasize, prev_datasize, *dz;
+	efi_guid_t vendor_guid, *vd = NULL;
+	efi_status_t status;
+	uint16_t *name = NULL;
+	uint32_t attr, *at;
+	void *data = NULL;
+	int rv = 0;
+
+	getvariable = (struct efi_getvariable __user *)arg;
+
+	if (copy_from_user(&getvariable_local, getvariable,
+			   sizeof(getvariable_local)))
+		return -EFAULT;
+	if (getvariable_local.data_size &&
+	    get_user(datasize, getvariable_local.data_size))
+		return -EFAULT;
+	if (getvariable_local.vendor_guid) {
+
+		if (copy_from_user(&vendor_guid, getvariable_local.vendor_guid,
+			   sizeof(vendor_guid)))
+			return -EFAULT;
+		vd = &vendor_guid;
+	}
+
+	if (getvariable_local.variable_name) {
+		rv = copy_ucs2_from_user(&name,
+				getvariable_local.variable_name);
+		if (rv)
+			return rv;
+	}
+
+	at = getvariable_local.attributes ? &attr : NULL;
+	dz = getvariable_local.data_size ? &datasize : NULL;
+
+	if (getvariable_local.data_size && getvariable_local.data) {
+		data = kmalloc(datasize, GFP_KERNEL);
+		if (!data) {
+			ucs2_kfree(name);
+			return -ENOMEM;
+		}
+	}
+
+	prev_datasize = datasize;
+	status = efi.get_variable(name, vd, at, dz, data);
+	ucs2_kfree(name);
+
+	if (data) {
+		if (status == EFI_SUCCESS && prev_datasize >= datasize)
+			rv = copy_to_user(getvariable_local.data, data,
+				datasize);
+		kfree(data);
+	}
+
+	if (rv)
+		return rv;
+
+	if (put_user(status, getvariable_local.status))
+		return -EFAULT;
+	if (status == EFI_SUCCESS && prev_datasize >= datasize) {
+		if (at && put_user(attr, getvariable_local.attributes))
+			return -EFAULT;
+		if (dz && put_user(datasize, getvariable_local.data_size))
+			return -EFAULT;
+		return 0;
+	} else {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static long efi_runtime_set_variable(unsigned long arg)
+{
+	struct efi_setvariable __user *setvariable;
+	struct efi_setvariable setvariable_local;
+	efi_guid_t vendor_guid;
+	efi_status_t status;
+	uint16_t *name;
+	void *data;
+	int rv;
+
+	setvariable = (struct efi_setvariable __user *)arg;
+
+	if (copy_from_user(&setvariable_local, setvariable,
+			   sizeof(setvariable_local)))
+		return -EFAULT;
+	if (copy_from_user(&vendor_guid, setvariable_local.vendor_guid,
+			   sizeof(vendor_guid)))
+		return -EFAULT;
+
+	rv = copy_ucs2_from_user(&name, setvariable_local.variable_name);
+	if (rv)
+		return rv;
+
+	data = kmalloc(setvariable_local.data_size, GFP_KERNEL);
+	if (!data) {
+		ucs2_kfree(name);
+		return -ENOMEM;
+	}
+	if (copy_from_user(data, setvariable_local.data,
+			   setvariable_local.data_size)) {
+		ucs2_kfree(data);
+		kfree(name);
+		return -EFAULT;
+	}
+
+	status = efi.set_variable(name, &vendor_guid,
+				setvariable_local.attributes,
+				setvariable_local.data_size, data);
+
+	kfree(data);
+	ucs2_kfree(name);
+
+	if (put_user(status, setvariable_local.status))
+		return -EFAULT;
+	return status == EFI_SUCCESS ? 0 : -EINVAL;
+}
+
+static long efi_runtime_get_time(unsigned long arg)
+{
+	struct efi_gettime __user *gettime;
+	struct efi_gettime  gettime_local;
+	efi_status_t status;
+	efi_time_cap_t cap;
+	efi_time_t efi_time;
+
+	gettime = (struct efi_gettime __user *)arg;
+	if (copy_from_user(&gettime_local, gettime, sizeof(gettime_local)))
+		return -EFAULT;
+
+	status = efi.get_time(gettime_local.time ? &efi_time : NULL,
+			      gettime_local.capabilities ? &cap : NULL);
+
+	if (put_user(status, gettime_local.status))
+		return -EFAULT;
+	if (status != EFI_SUCCESS) {
+		pr_err("efi_test: can't read time\n");
+		return -EINVAL;
+	}
+	if (gettime_local.capabilities) {
+		efi_time_cap_t __user *cap_local;
+
+		cap_local = (efi_time_cap_t *)gettime_local.capabilities;
+		if (put_user(cap.resolution,
+			&(cap_local->resolution)) ||
+			put_user(cap.accuracy, &(cap_local->accuracy)) ||
+			put_user(cap.sets_to_zero, &(cap_local->sets_to_zero)))
+			return -EFAULT;
+	}
+	if (gettime_local.time)
+		return copy_to_user(gettime_local.time, &efi_time,
+			sizeof(efi_time_t)) ? -EFAULT : 0;
+	return 0;
+}
+
+static long efi_runtime_set_time(unsigned long arg)
+{
+	struct efi_settime __user *settime;
+	struct efi_settime settime_local;
+	efi_status_t status;
+	efi_time_t efi_time;
+
+	settime = (struct efi_settime __user *)arg;
+	if (copy_from_user(&settime_local, settime, sizeof(settime_local)))
+		return -EFAULT;
+	if (copy_from_user(&efi_time, settime_local.time,
+					sizeof(efi_time_t)))
+		return -EFAULT;
+	status = efi.set_time(&efi_time);
+
+	if (put_user(status, settime_local.status))
+		return -EFAULT;
+
+	return status == EFI_SUCCESS ? 0 : -EINVAL;
+}
+
+static long efi_runtime_get_waketime(unsigned long arg)
+{
+	struct efi_getwakeuptime __user *getwakeuptime;
+	struct efi_getwakeuptime getwakeuptime_local;
+	unsigned char enabled, pending;
+	efi_status_t status;
+	efi_time_t efi_time;
+
+	getwakeuptime = (struct efi_getwakeuptime __user *)arg;
+	if (copy_from_user(&getwakeuptime_local, getwakeuptime,
+				sizeof(getwakeuptime_local)))
+		return -EFAULT;
+
+	status = efi.get_wakeup_time(
+		getwakeuptime_local.enabled ? (efi_bool_t *)&enabled : NULL,
+		getwakeuptime_local.pending ? (efi_bool_t *)&pending : NULL,
+		getwakeuptime_local.time ? &efi_time : NULL);
+
+	if (put_user(status, getwakeuptime_local.status))
+		return -EFAULT;
+	if (status != EFI_SUCCESS)
+		return -EINVAL;
+	if (getwakeuptime_local.enabled && put_user(enabled,
+						getwakeuptime_local.enabled))
+		return -EFAULT;
+
+	if (getwakeuptime_local.time)
+		return copy_to_user(getwakeuptime_local.time, &efi_time,
+			sizeof(efi_time_t)) ? -EFAULT : 0;
+	return 0;
+}
+
+static long efi_runtime_set_waketime(unsigned long arg)
+{
+	struct efi_setwakeuptime __user *setwakeuptime;
+	struct efi_setwakeuptime setwakeuptime_local;
+	unsigned char enabled;
+	efi_status_t status;
+	efi_time_t efi_time;
+
+	setwakeuptime = (struct efi_setwakeuptime __user *)arg;
+
+	if (copy_from_user(&setwakeuptime_local, setwakeuptime,
+				sizeof(setwakeuptime_local)))
+		return -EFAULT;
+
+	enabled = setwakeuptime_local.enabled;
+	if (setwakeuptime_local.time) {
+		if (copy_from_user(&efi_time, setwakeuptime_local.time,
+					sizeof(efi_time_t)))
+			return -EFAULT;
+
+		status = efi.set_wakeup_time(enabled, &efi_time);
+	} else {
+		status = efi.set_wakeup_time(enabled, NULL);
+	}
+
+	if (put_user(status, setwakeuptime_local.status))
+		return -EFAULT;
+
+	return status == EFI_SUCCESS ? 0 : -EINVAL;
+}
+
+static long efi_runtime_get_nextvariablename(unsigned long arg)
+{
+	struct efi_getnextvariablename __user *getnextvariablename;
+	struct efi_getnextvariablename getnextvariablename_local;
+	unsigned long name_size, prev_name_size = 0, *ns = NULL;
+	efi_status_t status;
+	efi_guid_t *vd = NULL;
+	efi_guid_t vendor_guid;
+	uint16_t *name = NULL;
+	int rv;
+
+	getnextvariablename = (struct efi_getnextvariablename
+							__user *)arg;
+
+	if (copy_from_user(&getnextvariablename_local, getnextvariablename,
+			   sizeof(getnextvariablename_local)))
+		return -EFAULT;
+
+	if (getnextvariablename_local.variable_name_size) {
+		if (get_user(name_size,
+				getnextvariablename_local.variable_name_size))
+			return -EFAULT;
+		ns = &name_size;
+		prev_name_size = name_size;
+	}
+
+	if (getnextvariablename_local.vendor_guid) {
+		if (copy_from_user(&vendor_guid,
+				getnextvariablename_local.vendor_guid,
+				sizeof(vendor_guid)))
+			return -EFAULT;
+		vd = &vendor_guid;
+	}
+
+	if (getnextvariablename_local.variable_name) {
+		size_t name_string_size = 0;
+
+		rv = get_ucs2_strsize_from_user(
+				getnextvariablename_local.variable_name,
+				&name_string_size);
+		if (rv)
+			return rv;
+		/*
+		 * name_size may be smaller than the real buffer size where
+		 * VariableName located in some use cases. The most typical
+		 * case is passing a 0 toget the required buffer size for the
+		 * 1st time call. So we need to copy the content from user
+		 * space for at least the string size ofVariableName, or else
+		 * the name passed to UEFI may not be terminatedas we expected.
+		 */
+		rv = copy_ucs2_from_user_len(&name,
+				getnextvariablename_local.variable_name,
+				prev_name_size > name_string_size ?
+				prev_name_size : name_string_size);
+		if (rv)
+			return rv;
+	}
+
+	status = efi.get_next_variable(ns, name, vd);
+
+	if (name) {
+		rv = copy_ucs2_to_user_len(
+				getnextvariablename_local.variable_name,
+				name, prev_name_size);
+		ucs2_kfree(name);
+		if (rv)
+			return -EFAULT;
+	}
+
+	if (put_user(status, getnextvariablename_local.status))
+		return -EFAULT;
+
+	if (ns) {
+		if (put_user(*ns,
+			getnextvariablename_local.variable_name_size))
+			return -EFAULT;
+	}
+
+	if (vd) {
+		if (copy_to_user(getnextvariablename_local.vendor_guid,
+				 vd, sizeof(efi_guid_t)))
+			return -EFAULT;
+	}
+
+	if (status != EFI_SUCCESS)
+		return -EINVAL;
+	return 0;
+}
+
+static long efi_runtime_get_nexthighmonocount(unsigned long arg)
+{
+	struct efi_getnexthighmonotoniccount __user *getnexthighmonotoniccount;
+	struct efi_getnexthighmonotoniccount getnexthighmonotoniccount_local;
+	efi_status_t status;
+	uint32_t count;
+
+	getnexthighmonotoniccount = (struct
+			efi_getnexthighmonotoniccount __user *)arg;
+
+	if (copy_from_user(&getnexthighmonotoniccount_local,
+			   getnexthighmonotoniccount,
+			   sizeof(getnexthighmonotoniccount_local)))
+		return -EFAULT;
+
+	status = efi.get_next_high_mono_count(
+		getnexthighmonotoniccount_local.high_count ? &count : NULL);
+
+	if (put_user(status, getnexthighmonotoniccount_local.status))
+		return -EFAULT;
+
+	if (getnexthighmonotoniccount_local.high_count &&
+	    put_user(count, getnexthighmonotoniccount_local.high_count))
+		return -EFAULT;
+
+	if (status != EFI_SUCCESS)
+		return -EINVAL;
+
+	return 0;
+}
+
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 1, 0)
+static long efi_runtime_query_variableinfo(unsigned long arg)
+{
+	struct efi_queryvariableinfo __user *queryvariableinfo;
+	struct efi_queryvariableinfo queryvariableinfo_local;
+	efi_status_t status;
+	uint64_t max_storage, remaining, max_size;
+
+	queryvariableinfo = (struct efi_queryvariableinfo __user *)arg;
+
+	if (copy_from_user(&queryvariableinfo_local, queryvariableinfo,
+			   sizeof(queryvariableinfo_local)))
+		return -EFAULT;
+
+	status = efi.query_variable_info(queryvariableinfo_local.attributes,
+					 &max_storage, &remaining, &max_size);
+
+	if (put_user(max_storage,
+		     queryvariableinfo_local.maximum_variable_storage_size))
+		return -EFAULT;
+
+	if (put_user(remaining,
+		     queryvariableinfo_local.remaining_variable_storage_size))
+		return -EFAULT;
+
+	if (put_user(max_size, queryvariableinfo_local.maximum_variable_size))
+		return -EFAULT;
+
+	if (put_user(status, queryvariableinfo_local.status))
+		return -EFAULT;
+	if (status != EFI_SUCCESS)
+		return -EINVAL;
+
+	return 0;
+}
+
+static long efi_runtime_query_capsulecaps(unsigned long arg)
+{
+	struct efi_querycapsulecapabilities __user *u_caps;
+	struct efi_querycapsulecapabilities caps;
+	efi_capsule_header_t *capsules;
+	efi_status_t status;
+	uint64_t max_size;
+	int i, reset_type;
+
+	u_caps = (struct efi_querycapsulecapabilities __user *)arg;
+
+	if (copy_from_user(&caps, u_caps, sizeof(caps)))
+		return -EFAULT;
+
+	capsules = kcalloc(caps.capsule_count + 1,
+			   sizeof(efi_capsule_header_t), GFP_KERNEL);
+	if (!capsules)
+		return -ENOMEM;
+
+	for (i = 0; i < caps.capsule_count; i++) {
+		efi_capsule_header_t *c;
+		/*
+		 * We cannot dereference caps.CapsuleHeaderArray directly to
+		 * obtain the address of the capsule as it resides in the
+		 * user space
+		 */
+		if (get_user(c, caps.capsule_header_array + i))
+			return -EFAULT;
+		if (copy_from_user(&capsules[i], c,
+				sizeof(efi_capsule_header_t)))
+			return -EFAULT;
+	}
+
+	caps.capsule_header_array = &capsules;
+
+	status = efi.query_capsule_caps((efi_capsule_header_t **)
+					caps.capsule_header_array,
+					caps.capsule_count,
+					&max_size, &reset_type);
+
+	if (put_user(status, caps.status))
+		return -EFAULT;
+
+	if (put_user(max_size, caps.maximum_capsule_size))
+		return -EFAULT;
+
+	if (put_user(reset_type, caps.reset_type))
+		return -EFAULT;
+
+	if (status != EFI_SUCCESS)
+		return -EINVAL;
+
+	return 0;
+}
+#endif
+
+static long efi_test_ioctl(struct file *file, unsigned int cmd,
+							unsigned long arg)
+{
+	switch (cmd) {
+	case EFI_RUNTIME_GET_VARIABLE:
+		return efi_runtime_get_variable(arg);
+
+	case EFI_RUNTIME_SET_VARIABLE:
+		return efi_runtime_set_variable(arg);
+
+	case EFI_RUNTIME_GET_TIME:
+		return efi_runtime_get_time(arg);
+
+	case EFI_RUNTIME_SET_TIME:
+		return efi_runtime_set_time(arg);
+
+	case EFI_RUNTIME_GET_WAKETIME:
+		return efi_runtime_get_waketime(arg);
+
+	case EFI_RUNTIME_SET_WAKETIME:
+		return efi_runtime_set_waketime(arg);
+
+	case EFI_RUNTIME_GET_NEXTVARIABLENAME:
+		return efi_runtime_get_nextvariablename(arg);
+
+	case EFI_RUNTIME_GET_NEXTHIGHMONOTONICCOUNT:
+		return efi_runtime_get_nexthighmonocount(arg);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 1, 0)
+	case EFI_RUNTIME_QUERY_VARIABLEINFO:
+		return efi_runtime_query_variableinfo(arg);
+
+	case EFI_RUNTIME_QUERY_CAPSULECAPABILITIES:
+		return efi_runtime_query_capsulecaps(arg);
+#endif
+	}
+
+	return -ENOTTY;
+}
+
+static int efi_test_open(struct inode *inode, struct file *file)
+{
+	/*
+	 * nothing special to do here
+	 * We do accept multiple open files at the same time as we
+	 * synchronize on the per call operation.
+	 */
+	return 0;
+}
+
+static int efi_test_close(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+/*
+ *	The various file operations we support.
+ */
+static const struct file_operations efi_test_fops = {
+	.owner		= THIS_MODULE,
+	.unlocked_ioctl	= efi_test_ioctl,
+	.open		= efi_test_open,
+	.release	= efi_test_close,
+	.llseek		= no_llseek,
+};
+
+static struct miscdevice efi_test_dev = {
+	MISC_DYNAMIC_MINOR,
+	"efi_test",
+	&efi_test_fops
+};
+
+static int __init efi_test_init(void)
+{
+	int ret;
+
+	if (!EFI_RUNTIME_ENABLED) {
+		pr_err("efi_test: EFI runtime services not enabled.\n");
+		return -ENODEV;
+	}
+
+	ret = misc_register(&efi_test_dev);
+	if (ret) {
+		pr_err("efi_test: can't misc_register on minor=%d\n",
+			MISC_DYNAMIC_MINOR);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void __exit efi_test_exit(void)
+{
+	misc_deregister(&efi_test_dev);
+}
+
+module_init(efi_test_init);
+module_exit(efi_test_exit);
diff --git a/drivers/firmware/efi/efi_test/efi_test.h b/drivers/firmware/efi/efi_test/efi_test.h
new file mode 100644
index 0000000..648030f
--- /dev/null
+++ b/drivers/firmware/efi/efi_test/efi_test.h
@@ -0,0 +1,110 @@ 
+/*
+ * EFI Test driver Header
+ *
+ * Copyright(C) 2012-2016 Canonical Ltd.
+ *
+ */
+
+#ifndef _DRIVERS_FIRMWARE_EFI_TEST_H_
+#define _DRIVERS_FIRMWARE_EFI_TEST_H_
+
+#include <linux/efi.h>
+
+struct efi_getvariable {
+	uint16_t	*variable_name;
+	efi_guid_t	*vendor_guid;
+	uint32_t	*attributes;
+	uint64_t	*data_size;
+	void		*data;
+	uint64_t	*status;
+} __packed;
+
+struct efi_setvariable {
+	uint16_t	*variable_name;
+	efi_guid_t	*vendor_guid;
+	uint32_t	attributes;
+	uint64_t	data_size;
+	void		*data;
+	uint64_t	*status;
+} __packed;
+
+struct efi_getnextvariablename {
+	uint64_t	*variable_name_size;
+	uint16_t	*variable_name;
+	efi_guid_t	*vendor_guid;
+	uint64_t	*status;
+} __packed;
+
+struct efi_queryvariableinfo {
+	uint32_t	attributes;
+	uint64_t	*maximum_variable_storage_size;
+	uint64_t	*remaining_variable_storage_size;
+	uint64_t	*maximum_variable_size;
+	uint64_t	*status;
+} __packed;
+
+struct efi_gettime {
+	efi_time_t		*time;
+	efi_time_cap_t		*capabilities;
+	uint64_t		*status;
+} __packed;
+
+struct efi_settime {
+	efi_time_t		*time;
+	uint64_t		*status;
+} __packed;
+
+struct efi_getwakeuptime {
+	uint8_t		*enabled;
+	uint8_t		*pending;
+	efi_time_t	*time;
+	uint64_t	*status;
+} __packed;
+
+struct efi_setwakeuptime {
+	uint8_t		enabled;
+	efi_time_t	*time;
+	uint64_t	*status;
+} __packed;
+
+struct efi_getnexthighmonotoniccount {
+	uint32_t	*high_count;
+	uint64_t	*status;
+} __packed;
+
+struct efi_querycapsulecapabilities {
+	efi_capsule_header_t	**capsule_header_array;
+	uint64_t		capsule_count;
+	uint64_t		*maximum_capsule_size;
+	int			*reset_type;
+	uint64_t		*status;
+} __packed;
+
+#define EFI_RUNTIME_GET_VARIABLE \
+	_IOWR('p', 0x01, struct efi_getvariable)
+#define EFI_RUNTIME_SET_VARIABLE \
+	_IOW('p', 0x02, struct efi_setvariable)
+
+#define EFI_RUNTIME_GET_TIME \
+	_IOR('p', 0x03, struct efi_gettime)
+#define EFI_RUNTIME_SET_TIME \
+	_IOW('p', 0x04, struct efi_settime)
+
+#define EFI_RUNTIME_GET_WAKETIME \
+	_IOR('p', 0x05, struct efi_getwakeuptime)
+#define EFI_RUNTIME_SET_WAKETIME \
+	_IOW('p', 0x06, struct efi_setwakeuptime)
+
+#define EFI_RUNTIME_GET_NEXTVARIABLENAME \
+	_IOWR('p', 0x07, struct efi_getnextvariablename)
+
+#define EFI_RUNTIME_QUERY_VARIABLEINFO \
+	_IOR('p', 0x08, struct efi_queryvariableinfo)
+
+#define EFI_RUNTIME_GET_NEXTHIGHMONOTONICCOUNT \
+	_IOR('p', 0x09, struct efi_getnexthighmonotoniccount)
+
+#define EFI_RUNTIME_QUERY_CAPSULECAPABILITIES \
+	_IOR('p', 0x0A, struct efi_querycapsulecapabilities)
+
+#endif /* _DRIVERS_FIRMWARE_EFI_TEST_H_ */