From: Sai Praneeth <sai.praneeth.prak...@intel.com>

When a process requests the kernel to execute any efi_runtime_service(),
the requested efi_runtime_service (represented as an identifier) and its
arguments are packed into a struct named efi_runtime_work and queued
onto work queue named efi_rts_wq. The caller then waits until the work
is completed.

Introduce some infrastructure:
1. Creating workqueue named efi_rts_wq
2. A macro (efi_queue_work()) that
        a. Populates efi_runtime_work
        b. Queues work onto efi_rts_wq and
        c. Waits until worker thread completes

The caller thread has to wait until the worker thread completes, because
it depends on the return status of efi_runtime_service() and, in
specific cases, the arguments populated by efi_runtime_service(). Some
efi_runtime_services() takes a pointer to buffer as an argument and
fills up the buffer with requested data. For instance,
efi_get_variable() and efi_get_next_variable(). Hence, caller process
cannot just post the work and get going.

Some facts about efi_runtime_services():
1. A quick look at all the efi_runtime_services() shows that any
efi_runtime_service() has five or less arguments.
2. An argument of efi_runtime_service() can be a value (of any type)
or a pointer (of any type).
Hence, efi_runtime_work has five void pointers to store these arguments.

Signed-off-by: Sai Praneeth Prakhya <sai.praneeth.prak...@intel.com>
Suggested-by: Andy Lutomirski <l...@kernel.org>
Cc: Lee Chun-Yi <j...@suse.com>
Cc: Borislav Petkov <b...@alien8.de>
Cc: Tony Luck <tony.l...@intel.com>
Cc: Will Deacon <will.dea...@arm.com>
Cc: Dave Hansen <dave.han...@intel.com>
Cc: Mark Rutland <mark.rutl...@arm.com>
Cc: Bhupesh Sharma <bhsha...@redhat.com>
Cc: Naresh Bhat <naresh.b...@linaro.org>
Cc: Ricardo Neri <ricardo.n...@intel.com>
Cc: Peter Zijlstra <pet...@infradead.org>
Cc: Ravi Shankar <ravi.v.shan...@intel.com>
Cc: Matt Fleming <m...@codeblueprint.co.uk>
Cc: Dan Williams <dan.j.willi...@intel.com>
Cc: Ard Biesheuvel <ard.biesheu...@linaro.org>
Cc: Miguel Ojeda <miguel.ojeda.sando...@gmail.com>
---
 drivers/firmware/efi/efi.c              | 14 ++++++
 drivers/firmware/efi/runtime-wrappers.c | 85 +++++++++++++++++++++++++++++++++
 include/linux/efi.h                     |  3 ++
 3 files changed, 102 insertions(+)

diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c
index 1176af664013..2632294eb33f 100644
--- a/drivers/firmware/efi/efi.c
+++ b/drivers/firmware/efi/efi.c
@@ -84,6 +84,8 @@ struct mm_struct efi_mm = {
        .mmlist                 = LIST_HEAD_INIT(efi_mm.mmlist),
 };
 
+struct workqueue_struct *efi_rts_wq;
+
 static bool disable_runtime;
 static int __init setup_noefi(char *arg)
 {
@@ -338,6 +340,18 @@ static int __init efisubsys_init(void)
                return 0;
 
        /*
+        * Since we process only one efi_runtime_service() at a time, an
+        * ordered workqueue (which creates only one execution context)
+        * should suffice all our needs.
+        */
+       efi_rts_wq = alloc_ordered_workqueue("efi_rts_wq", 0);
+       if (!efi_rts_wq) {
+               pr_err("Creating efi_rts_wq failed, EFI runtime services 
disabled.\n");
+               clear_bit(EFI_RUNTIME_SERVICES, &efi.flags);
+               return 0;
+       }
+
+       /*
         * Clean DUMMY object calls EFI Runtime Service, set_variable(), so
         * it should be invoked only after efi_rts_wq is ready.
         */
diff --git a/drivers/firmware/efi/runtime-wrappers.c 
b/drivers/firmware/efi/runtime-wrappers.c
index ae54870b2788..534bd348feca 100644
--- a/drivers/firmware/efi/runtime-wrappers.c
+++ b/drivers/firmware/efi/runtime-wrappers.c
@@ -1,6 +1,15 @@
 /*
  * runtime-wrappers.c - Runtime Services function call wrappers
  *
+ * Implementation summary:
+ * -----------------------
+ * 1. When user/kernel thread requests to execute efi_runtime_service(),
+ * enqueue work to efi_rts_wq.
+ * 2. Caller thread waits for completion until the work is finished
+ * because it's dependent on the return status and execution of
+ * efi_runtime_service().
+ * For instance, get_variable() and get_next_variable().
+ *
  * Copyright (C) 2014 Linaro Ltd. <ard.biesheu...@linaro.org>
  *
  * Split off from arch/x86/platform/efi/efi.c
@@ -22,6 +31,9 @@
 #include <linux/mutex.h>
 #include <linux/semaphore.h>
 #include <linux/stringify.h>
+#include <linux/workqueue.h>
+#include <linux/completion.h>
+
 #include <asm/efi.h>
 
 /*
@@ -33,6 +45,79 @@
 #define __efi_call_virt(f, args...) \
        __efi_call_virt_pointer(efi.systab->runtime, f, args)
 
+/* efi_runtime_service() function identifiers */
+enum efi_rts_ids {
+       GET_TIME,
+       SET_TIME,
+       GET_WAKEUP_TIME,
+       SET_WAKEUP_TIME,
+       GET_VARIABLE,
+       GET_NEXT_VARIABLE,
+       SET_VARIABLE,
+       SET_VARIABLE_NONBLOCKING,
+       QUERY_VARIABLE_INFO,
+       QUERY_VARIABLE_INFO_NONBLOCKING,
+       GET_NEXT_HIGH_MONO_COUNT,
+       RESET_SYSTEM,
+       UPDATE_CAPSULE,
+       QUERY_CAPSULE_CAPS,
+};
+
+/*
+ * efi_runtime_work:   Details of EFI Runtime Service work
+ * @arg<1-5>:          EFI Runtime Service function arguments
+ * @status:            Status of executing EFI Runtime Service
+ * @efi_rts_id:                EFI Runtime Service function identifier
+ * @efi_rts_comp:      Struct used for handling completions
+ */
+struct efi_runtime_work {
+       void *arg1;
+       void *arg2;
+       void *arg3;
+       void *arg4;
+       void *arg5;
+       efi_status_t status;
+       struct work_struct work;
+       enum efi_rts_ids efi_rts_id;
+       struct completion efi_rts_comp;
+};
+
+/*
+ * efi_queue_work:     Queue efi_runtime_service() and wait until it's done
+ * @rts:               efi_runtime_service() function identifier
+ * @rts_arg<1-5>:      efi_runtime_service() function arguments
+ *
+ * Accesses to efi_runtime_services() are serialized by a binary
+ * semaphore (efi_runtime_lock) and caller waits until the work is
+ * finished, hence _only_ one work is queued at a time and the caller
+ * thread waits for completion.
+ */
+#define efi_queue_work(_rts, _arg1, _arg2, _arg3, _arg4, _arg5)                
\
+({                                                                     \
+       struct efi_runtime_work efi_rts_work;                           \
+       efi_rts_work.status = EFI_ABORTED;                              \
+                                                                       \
+       init_completion(&efi_rts_work.efi_rts_comp);                    \
+       INIT_WORK_ONSTACK(&efi_rts_work.work, efi_call_rts);            \
+       efi_rts_work.arg1 = _arg1;                                      \
+       efi_rts_work.arg2 = _arg2;                                      \
+       efi_rts_work.arg3 = _arg3;                                      \
+       efi_rts_work.arg4 = _arg4;                                      \
+       efi_rts_work.arg5 = _arg5;                                      \
+       efi_rts_work.efi_rts_id = _rts;                                 \
+                                                                       \
+       /*                                                              \
+        * queue_work() returns 0 if work was already on queue,         \
+        * _ideally_ this should never happen.                          \
+        */                                                             \
+       if (queue_work(efi_rts_wq, &efi_rts_work.work))                 \
+               wait_for_completion(&efi_rts_work.efi_rts_comp);        \
+       else                                                            \
+               pr_err("Failed to queue work to efi_rts_wq.\n");        \
+                                                                       \
+       efi_rts_work.status;                                            \
+})
+
 void efi_call_virt_check_flags(unsigned long flags, const char *call)
 {
        unsigned long cur_flags, mismatch;
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 1b79939d0b1e..8fb1af15be67 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1654,4 +1654,7 @@ struct linux_efi_tpm_eventlog {
 
 extern int efi_tpm_eventlog_init(void);
 
+/* Workqueue to queue EFI Runtime Services */
+extern struct workqueue_struct *efi_rts_wq;
+
 #endif /* _LINUX_EFI_H */
-- 
2.7.4

Reply via email to