According to section 7.1 of the UEFI spec, Runtime Services are not fully reentrant, and there are particular combinations of calls that need to be serialized. Signed-off-by: Ard Biesheuvel <ard.biesheuvel@xxxxxxxxxx> --- drivers/firmware/efi/runtime-wrappers.c | 109 +++++++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 10 deletions(-) diff --git a/drivers/firmware/efi/runtime-wrappers.c b/drivers/firmware/efi/runtime-wrappers.c index 10daa4bbb258..6588d054af99 100644 --- a/drivers/firmware/efi/runtime-wrappers.c +++ b/drivers/firmware/efi/runtime-wrappers.c @@ -15,10 +15,50 @@ */ #include <linux/efi.h> -#include <linux/spinlock.h> /* spinlock_t */ +#include <linux/mutex.h> +#include <linux/spinlock.h> #include <asm/efi.h> /* + * According to section 7.1 of the UEFI spec, Runtime Services are not fully + * reentrant, and there are particular combinations of calls that need to be + * serialized. + * + * Table 31. Rules for Reentry Into Runtime Services + * +------------------------------------+-------------------------------+ + * | If previous call is busy in | Forbidden to call | + * +------------------------------------+-------------------------------+ + * | Any | SetVirtualAddressMap() | + * +------------------------------------+-------------------------------+ + * | ConvertPointer() | ConvertPointer() | + * +------------------------------------+-------------------------------+ + * | SetVariable() | ResetSystem() | + * | UpdateCapsule() | | + * | SetTime() | | + * | SetWakeupTime() | | + * | GetNextHighMonotonicCount() | | + * +------------------------------------+-------------------------------+ + * | GetVariable() | GetVariable() | + * | GetNextVariableName() | GetNextVariableName() | + * | SetVariable() | SetVariable() | + * | QueryVariableInfo() | QueryVariableInfo() | + * | UpdateCapsule() | UpdateCapsule() | + * | QueryCapsuleCapabilities() | QueryCapsuleCapabilities() | + * | GetNextHighMonotonicCount() | GetNextHighMonotonicCount() | + * +------------------------------------+-------------------------------+ + * | GetTime() | GetTime() | + * | SetTime() | SetTime() | + * | GetWakeupTime() | GetWakeupTime() | + * | SetWakeupTime() | SetWakeupTime() | + * +------------------------------------+-------------------------------+ + * + * The first two are disregarded here, as SetVirtualAddressMap() is called + * only once, and very early, and ConvertPointer() is not exposed at all. + */ +static DEFINE_MUTEX(var_ro_mutex); +static DEFINE_MUTEX(var_rw_mutex); + +/* * As per commit ef68c8f87ed1 ("x86: Serialize EFI time accesses on rtc_lock"), * the EFI specification requires that callers of the time related runtime * functions serialize with other CMOS accesses in the kernel, as the EFI time @@ -78,14 +118,25 @@ static efi_status_t virt_efi_get_variable(efi_char16_t *name, unsigned long *data_size, void *data) { - return efi_call_virt(get_variable, name, vendor, attr, data_size, data); + efi_status_t status; + + mutex_lock(&var_ro_mutex); + status = efi_call_virt(get_variable, name, vendor, attr, data_size, + data); + mutex_unlock(&var_ro_mutex); + return status; } static efi_status_t virt_efi_get_next_variable(unsigned long *name_size, efi_char16_t *name, efi_guid_t *vendor) { - return efi_call_virt(get_next_variable, name_size, name, vendor); + efi_status_t status; + + mutex_lock(&var_ro_mutex); + status = efi_call_virt(get_next_variable, name_size, name, vendor); + mutex_unlock(&var_ro_mutex); + return status; } static efi_status_t virt_efi_set_variable(efi_char16_t *name, @@ -94,7 +145,15 @@ static efi_status_t virt_efi_set_variable(efi_char16_t *name, unsigned long data_size, void *data) { - return efi_call_virt(set_variable, name, vendor, attr, data_size, data); + efi_status_t status; + + mutex_lock(&var_ro_mutex); + mutex_lock(&var_rw_mutex); + status = efi_call_virt(set_variable, name, vendor, attr, data_size, + data); + mutex_unlock(&var_rw_mutex); + mutex_unlock(&var_ro_mutex); + return status; } static efi_status_t virt_efi_query_variable_info(u32 attr, @@ -102,16 +161,28 @@ static efi_status_t virt_efi_query_variable_info(u32 attr, u64 *remaining_space, u64 *max_variable_size) { + efi_status_t status; + if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION) return EFI_UNSUPPORTED; - return efi_call_virt(query_variable_info, attr, storage_space, - remaining_space, max_variable_size); + mutex_lock(&var_ro_mutex); + status = efi_call_virt(query_variable_info, attr, storage_space, + remaining_space, max_variable_size); + mutex_unlock(&var_ro_mutex); + return status; } static efi_status_t virt_efi_get_next_high_mono_count(u32 *count) { - return efi_call_virt(get_next_high_mono_count, count); + efi_status_t status; + + mutex_lock(&var_ro_mutex); + mutex_lock(&var_rw_mutex); + status = efi_call_virt(get_next_high_mono_count, count); + mutex_unlock(&var_rw_mutex); + mutex_unlock(&var_ro_mutex); + return status; } static void virt_efi_reset_system(int reset_type, @@ -119,17 +190,30 @@ static void virt_efi_reset_system(int reset_type, unsigned long data_size, efi_char16_t *data) { + unsigned long flags; + + mutex_lock(&var_rw_mutex); + spin_lock_irqsave(&rtc_lock, flags); __efi_call_virt(reset_system, reset_type, status, data_size, data); + spin_unlock_irqrestore(&rtc_lock, flags); + mutex_unlock(&var_rw_mutex); } static efi_status_t virt_efi_update_capsule(efi_capsule_header_t **capsules, unsigned long count, unsigned long sg_list) { + efi_status_t status; + if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION) return EFI_UNSUPPORTED; - return efi_call_virt(update_capsule, capsules, count, sg_list); + mutex_lock(&var_ro_mutex); + mutex_lock(&var_rw_mutex); + status = efi_call_virt(update_capsule, capsules, count, sg_list); + mutex_unlock(&var_rw_mutex); + mutex_unlock(&var_ro_mutex); + return status; } static efi_status_t virt_efi_query_capsule_caps(efi_capsule_header_t **capsules, @@ -137,11 +221,16 @@ static efi_status_t virt_efi_query_capsule_caps(efi_capsule_header_t **capsules, u64 *max_size, int *reset_type) { + efi_status_t status; + if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION) return EFI_UNSUPPORTED; - return efi_call_virt(query_capsule_caps, capsules, count, max_size, - reset_type); + mutex_lock(&var_ro_mutex); + status = efi_call_virt(query_capsule_caps, capsules, count, max_size, + reset_type); + mutex_unlock(&var_ro_mutex); + return status; } void efi_native_runtime_setup(void) -- 1.8.3.2 -- To unsubscribe from this list: send the line "unsubscribe linux-efi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html