|
@@ -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.biesheuvel@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,76 @@
|
|
|
#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,
|
|
|
+ QUERY_VARIABLE_INFO,
|
|
|
+ GET_NEXT_HIGH_MONO_COUNT,
|
|
|
+ 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;
|
|
@@ -90,13 +172,98 @@ void efi_call_virt_check_flags(unsigned long flags, const char *call)
|
|
|
*/
|
|
|
static DEFINE_SEMAPHORE(efi_runtime_lock);
|
|
|
|
|
|
+/*
|
|
|
+ * Calls the appropriate efi_runtime_service() with the appropriate
|
|
|
+ * arguments.
|
|
|
+ *
|
|
|
+ * Semantics followed by efi_call_rts() to understand efi_runtime_work:
|
|
|
+ * 1. If argument was a pointer, recast it from void pointer to original
|
|
|
+ * pointer type.
|
|
|
+ * 2. If argument was a value, recast it from void pointer to original
|
|
|
+ * pointer type and dereference it.
|
|
|
+ */
|
|
|
+static void efi_call_rts(struct work_struct *work)
|
|
|
+{
|
|
|
+ struct efi_runtime_work *efi_rts_work;
|
|
|
+ void *arg1, *arg2, *arg3, *arg4, *arg5;
|
|
|
+ efi_status_t status = EFI_NOT_FOUND;
|
|
|
+
|
|
|
+ efi_rts_work = container_of(work, struct efi_runtime_work, work);
|
|
|
+ arg1 = efi_rts_work->arg1;
|
|
|
+ arg2 = efi_rts_work->arg2;
|
|
|
+ arg3 = efi_rts_work->arg3;
|
|
|
+ arg4 = efi_rts_work->arg4;
|
|
|
+ arg5 = efi_rts_work->arg5;
|
|
|
+
|
|
|
+ switch (efi_rts_work->efi_rts_id) {
|
|
|
+ case GET_TIME:
|
|
|
+ status = efi_call_virt(get_time, (efi_time_t *)arg1,
|
|
|
+ (efi_time_cap_t *)arg2);
|
|
|
+ break;
|
|
|
+ case SET_TIME:
|
|
|
+ status = efi_call_virt(set_time, (efi_time_t *)arg1);
|
|
|
+ break;
|
|
|
+ case GET_WAKEUP_TIME:
|
|
|
+ status = efi_call_virt(get_wakeup_time, (efi_bool_t *)arg1,
|
|
|
+ (efi_bool_t *)arg2, (efi_time_t *)arg3);
|
|
|
+ break;
|
|
|
+ case SET_WAKEUP_TIME:
|
|
|
+ status = efi_call_virt(set_wakeup_time, *(efi_bool_t *)arg1,
|
|
|
+ (efi_time_t *)arg2);
|
|
|
+ break;
|
|
|
+ case GET_VARIABLE:
|
|
|
+ status = efi_call_virt(get_variable, (efi_char16_t *)arg1,
|
|
|
+ (efi_guid_t *)arg2, (u32 *)arg3,
|
|
|
+ (unsigned long *)arg4, (void *)arg5);
|
|
|
+ break;
|
|
|
+ case GET_NEXT_VARIABLE:
|
|
|
+ status = efi_call_virt(get_next_variable, (unsigned long *)arg1,
|
|
|
+ (efi_char16_t *)arg2,
|
|
|
+ (efi_guid_t *)arg3);
|
|
|
+ break;
|
|
|
+ case SET_VARIABLE:
|
|
|
+ status = efi_call_virt(set_variable, (efi_char16_t *)arg1,
|
|
|
+ (efi_guid_t *)arg2, *(u32 *)arg3,
|
|
|
+ *(unsigned long *)arg4, (void *)arg5);
|
|
|
+ break;
|
|
|
+ case QUERY_VARIABLE_INFO:
|
|
|
+ status = efi_call_virt(query_variable_info, *(u32 *)arg1,
|
|
|
+ (u64 *)arg2, (u64 *)arg3, (u64 *)arg4);
|
|
|
+ break;
|
|
|
+ case GET_NEXT_HIGH_MONO_COUNT:
|
|
|
+ status = efi_call_virt(get_next_high_mono_count, (u32 *)arg1);
|
|
|
+ break;
|
|
|
+ case UPDATE_CAPSULE:
|
|
|
+ status = efi_call_virt(update_capsule,
|
|
|
+ (efi_capsule_header_t **)arg1,
|
|
|
+ *(unsigned long *)arg2,
|
|
|
+ *(unsigned long *)arg3);
|
|
|
+ break;
|
|
|
+ case QUERY_CAPSULE_CAPS:
|
|
|
+ status = efi_call_virt(query_capsule_caps,
|
|
|
+ (efi_capsule_header_t **)arg1,
|
|
|
+ *(unsigned long *)arg2, (u64 *)arg3,
|
|
|
+ (int *)arg4);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ /*
|
|
|
+ * Ideally, we should never reach here because a caller of this
|
|
|
+ * function should have put the right efi_runtime_service()
|
|
|
+ * function identifier into efi_rts_work->efi_rts_id
|
|
|
+ */
|
|
|
+ pr_err("Requested executing invalid EFI Runtime Service.\n");
|
|
|
+ }
|
|
|
+ efi_rts_work->status = status;
|
|
|
+ complete(&efi_rts_work->efi_rts_comp);
|
|
|
+}
|
|
|
+
|
|
|
static efi_status_t virt_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc)
|
|
|
{
|
|
|
efi_status_t status;
|
|
|
|
|
|
if (down_interruptible(&efi_runtime_lock))
|
|
|
return EFI_ABORTED;
|
|
|
- status = efi_call_virt(get_time, tm, tc);
|
|
|
+ status = efi_queue_work(GET_TIME, tm, tc, NULL, NULL, NULL);
|
|
|
up(&efi_runtime_lock);
|
|
|
return status;
|
|
|
}
|
|
@@ -107,7 +274,7 @@ static efi_status_t virt_efi_set_time(efi_time_t *tm)
|
|
|
|
|
|
if (down_interruptible(&efi_runtime_lock))
|
|
|
return EFI_ABORTED;
|
|
|
- status = efi_call_virt(set_time, tm);
|
|
|
+ status = efi_queue_work(SET_TIME, tm, NULL, NULL, NULL, NULL);
|
|
|
up(&efi_runtime_lock);
|
|
|
return status;
|
|
|
}
|
|
@@ -120,7 +287,8 @@ static efi_status_t virt_efi_get_wakeup_time(efi_bool_t *enabled,
|
|
|
|
|
|
if (down_interruptible(&efi_runtime_lock))
|
|
|
return EFI_ABORTED;
|
|
|
- status = efi_call_virt(get_wakeup_time, enabled, pending, tm);
|
|
|
+ status = efi_queue_work(GET_WAKEUP_TIME, enabled, pending, tm, NULL,
|
|
|
+ NULL);
|
|
|
up(&efi_runtime_lock);
|
|
|
return status;
|
|
|
}
|
|
@@ -131,7 +299,8 @@ static efi_status_t virt_efi_set_wakeup_time(efi_bool_t enabled, efi_time_t *tm)
|
|
|
|
|
|
if (down_interruptible(&efi_runtime_lock))
|
|
|
return EFI_ABORTED;
|
|
|
- status = efi_call_virt(set_wakeup_time, enabled, tm);
|
|
|
+ status = efi_queue_work(SET_WAKEUP_TIME, &enabled, tm, NULL, NULL,
|
|
|
+ NULL);
|
|
|
up(&efi_runtime_lock);
|
|
|
return status;
|
|
|
}
|
|
@@ -146,8 +315,8 @@ static efi_status_t virt_efi_get_variable(efi_char16_t *name,
|
|
|
|
|
|
if (down_interruptible(&efi_runtime_lock))
|
|
|
return EFI_ABORTED;
|
|
|
- status = efi_call_virt(get_variable, name, vendor, attr, data_size,
|
|
|
- data);
|
|
|
+ status = efi_queue_work(GET_VARIABLE, name, vendor, attr, data_size,
|
|
|
+ data);
|
|
|
up(&efi_runtime_lock);
|
|
|
return status;
|
|
|
}
|
|
@@ -160,7 +329,8 @@ static efi_status_t virt_efi_get_next_variable(unsigned long *name_size,
|
|
|
|
|
|
if (down_interruptible(&efi_runtime_lock))
|
|
|
return EFI_ABORTED;
|
|
|
- status = efi_call_virt(get_next_variable, name_size, name, vendor);
|
|
|
+ status = efi_queue_work(GET_NEXT_VARIABLE, name_size, name, vendor,
|
|
|
+ NULL, NULL);
|
|
|
up(&efi_runtime_lock);
|
|
|
return status;
|
|
|
}
|
|
@@ -175,8 +345,8 @@ static efi_status_t virt_efi_set_variable(efi_char16_t *name,
|
|
|
|
|
|
if (down_interruptible(&efi_runtime_lock))
|
|
|
return EFI_ABORTED;
|
|
|
- status = efi_call_virt(set_variable, name, vendor, attr, data_size,
|
|
|
- data);
|
|
|
+ status = efi_queue_work(SET_VARIABLE, name, vendor, &attr, &data_size,
|
|
|
+ data);
|
|
|
up(&efi_runtime_lock);
|
|
|
return status;
|
|
|
}
|
|
@@ -210,8 +380,8 @@ static efi_status_t virt_efi_query_variable_info(u32 attr,
|
|
|
|
|
|
if (down_interruptible(&efi_runtime_lock))
|
|
|
return EFI_ABORTED;
|
|
|
- status = efi_call_virt(query_variable_info, attr, storage_space,
|
|
|
- remaining_space, max_variable_size);
|
|
|
+ status = efi_queue_work(QUERY_VARIABLE_INFO, &attr, storage_space,
|
|
|
+ remaining_space, max_variable_size, NULL);
|
|
|
up(&efi_runtime_lock);
|
|
|
return status;
|
|
|
}
|
|
@@ -242,7 +412,8 @@ static efi_status_t virt_efi_get_next_high_mono_count(u32 *count)
|
|
|
|
|
|
if (down_interruptible(&efi_runtime_lock))
|
|
|
return EFI_ABORTED;
|
|
|
- status = efi_call_virt(get_next_high_mono_count, count);
|
|
|
+ status = efi_queue_work(GET_NEXT_HIGH_MONO_COUNT, count, NULL, NULL,
|
|
|
+ NULL, NULL);
|
|
|
up(&efi_runtime_lock);
|
|
|
return status;
|
|
|
}
|
|
@@ -272,7 +443,8 @@ static efi_status_t virt_efi_update_capsule(efi_capsule_header_t **capsules,
|
|
|
|
|
|
if (down_interruptible(&efi_runtime_lock))
|
|
|
return EFI_ABORTED;
|
|
|
- status = efi_call_virt(update_capsule, capsules, count, sg_list);
|
|
|
+ status = efi_queue_work(UPDATE_CAPSULE, capsules, &count, &sg_list,
|
|
|
+ NULL, NULL);
|
|
|
up(&efi_runtime_lock);
|
|
|
return status;
|
|
|
}
|
|
@@ -289,8 +461,8 @@ static efi_status_t virt_efi_query_capsule_caps(efi_capsule_header_t **capsules,
|
|
|
|
|
|
if (down_interruptible(&efi_runtime_lock))
|
|
|
return EFI_ABORTED;
|
|
|
- status = efi_call_virt(query_capsule_caps, capsules, count, max_size,
|
|
|
- reset_type);
|
|
|
+ status = efi_queue_work(QUERY_CAPSULE_CAPS, capsules, &count,
|
|
|
+ max_size, reset_type, NULL);
|
|
|
up(&efi_runtime_lock);
|
|
|
return status;
|
|
|
}
|