2013-04-10 02:42:13

by Matthew Garrett

[permalink] [raw]
Subject: [PATCH 1/3] efi: Determine how much space is used by boot services-only variables

EFI variables can be flagged as being accessible only within boot services.
This makes it awkward for us to figure out how much space they use at
runtime. In theory we could figure this out by simply comparing the results
from QueryVariableInfo() to the space used by all of our variables, but
that fails if the platform doesn't garbage collect on every boot. Thankfully,
calling QueryVariableInfo() while still inside boot services gives a more
reliable answer. This patch passes that information from the EFI boot stub
up to the efivars code, letting us calculate a reasonably accurate value.

Signed-off-by: Matthew Garrett <[email protected]>
---
arch/x86/boot/compressed/eboot.c | 47 +++++++++++++++++++++++++++++++++++
arch/x86/include/asm/efi.h | 10 ++++++++
arch/x86/include/uapi/asm/bootparam.h | 1 +
arch/x86/platform/efi/efi.c | 24 ++++++++++++++++++
drivers/firmware/efivars.c | 29 +++++++++++++++++++++
5 files changed, 111 insertions(+)

diff --git a/arch/x86/boot/compressed/eboot.c b/arch/x86/boot/compressed/eboot.c
index c205035..8615f75 100644
--- a/arch/x86/boot/compressed/eboot.c
+++ b/arch/x86/boot/compressed/eboot.c
@@ -251,6 +251,51 @@ static void find_bits(unsigned long mask, u8 *pos, u8 *size)
*size = len;
}

+static efi_status_t setup_efi_vars(struct boot_params *params)
+{
+ struct setup_data *data;
+ struct efi_var_bootdata *efidata;
+ u64 store_size, remaining_size, var_size;
+ efi_status_t status;
+
+ if (!sys_table->runtime->query_variable_info)
+ return EFI_UNSUPPORTED;
+
+ data = (struct setup_data *)(unsigned long)params->hdr.setup_data;
+
+ while (data && data->next)
+ data = (struct setup_data *)(unsigned long)data->next;
+
+ status = efi_call_phys4(sys_table->runtime->query_variable_info,
+ EFI_VARIABLE_NON_VOLATILE |
+ EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ EFI_VARIABLE_RUNTIME_ACCESS, &store_size,
+ &remaining_size, &var_size);
+
+ if (status != EFI_SUCCESS)
+ return status;
+
+ status = efi_call_phys3(sys_table->boottime->allocate_pool,
+ EFI_LOADER_DATA, sizeof(*efidata), &efidata);
+
+ if (status != EFI_SUCCESS)
+ return status;
+
+ efidata->data.type = SETUP_EFI_VARS;
+ efidata->data.len = sizeof(struct efi_var_bootdata) -
+ sizeof(struct setup_data);
+ efidata->data.next = 0;
+ efidata->store_size = store_size;
+ efidata->remaining_size = remaining_size;
+ efidata->max_var_size = var_size;
+
+ if (data)
+ data->next = (unsigned long)efidata;
+ else
+ params->hdr.setup_data = (unsigned long)efidata;
+
+}
+
static efi_status_t setup_efi_pci(struct boot_params *params)
{
efi_pci_io_protocol *pci;
@@ -1157,6 +1202,8 @@ struct boot_params *efi_main(void *handle, efi_system_table_t *_table,

setup_graphics(boot_params);

+ setup_efi_vars(boot_params);
+
setup_efi_pci(boot_params);

status = efi_call_phys3(sys_table->boottime->allocate_pool,
diff --git a/arch/x86/include/asm/efi.h b/arch/x86/include/asm/efi.h
index 60c89f3..6c3a154 100644
--- a/arch/x86/include/asm/efi.h
+++ b/arch/x86/include/asm/efi.h
@@ -93,6 +93,9 @@ extern void __iomem *efi_ioremap(unsigned long addr, unsigned long size,

#endif /* CONFIG_X86_32 */

+extern u64 efi_var_store_size;
+extern u64 efi_var_remaining_size;
+extern u64 efi_var_max_var_size;
extern int add_efi_memmap;
extern unsigned long x86_efi_facility;
extern void efi_set_executable(efi_memory_desc_t *md, bool executable);
@@ -102,6 +105,13 @@ extern void efi_call_phys_epilog(void);
extern void efi_unmap_memmap(void);
extern void efi_memory_uc(u64 addr, unsigned long size);

+struct efi_var_bootdata {
+ struct setup_data data;
+ u64 store_size;
+ u64 remaining_size;
+ u64 max_var_size;
+};
+
#ifdef CONFIG_EFI

static inline bool efi_is_native(void)
diff --git a/arch/x86/include/uapi/asm/bootparam.h b/arch/x86/include/uapi/asm/bootparam.h
index c15ddaf..0874424 100644
--- a/arch/x86/include/uapi/asm/bootparam.h
+++ b/arch/x86/include/uapi/asm/bootparam.h
@@ -6,6 +6,7 @@
#define SETUP_E820_EXT 1
#define SETUP_DTB 2
#define SETUP_PCI 3
+#define SETUP_EFI_VARS 4

/* ram_size flags */
#define RAMDISK_IMAGE_START_MASK 0x07FF
diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c
index c89c245..659da48 100644
--- a/arch/x86/platform/efi/efi.c
+++ b/arch/x86/platform/efi/efi.c
@@ -71,6 +71,13 @@ static efi_system_table_t efi_systab __initdata;

unsigned long x86_efi_facility;

+u64 efi_var_store_size;
+EXPORT_SYMBOL(efi_var_store_size);
+u64 efi_var_remaining_size;
+EXPORT_SYMBOL(efi_var_remaining_size);
+u64 efi_var_max_var_size;
+EXPORT_SYMBOL(efi_var_max_var_size);
+
/*
* Returns 1 if 'facility' is enabled, 0 otherwise.
*/
@@ -682,6 +689,9 @@ void __init efi_init(void)
char vendor[100] = "unknown";
int i = 0;
void *tmp;
+ struct setup_data *data;
+ struct efi_var_bootdata *efi_var_data;
+ u64 pa_data;

#ifdef CONFIG_X86_32
if (boot_params.efi_info.efi_systab_hi ||
@@ -699,6 +709,20 @@ void __init efi_init(void)
if (efi_systab_init(efi_phys.systab))
return;

+ pa_data = boot_params.hdr.setup_data;
+ while (pa_data) {
+ data = early_ioremap(pa_data, sizeof(*efi_var_data));
+ if (data->type == SETUP_EFI_VARS) {
+ efi_var_data = (struct efi_var_bootdata *)data;
+
+ efi_var_store_size = efi_var_data->store_size;
+ efi_var_remaining_size = efi_var_data->remaining_size;
+ efi_var_max_var_size = efi_var_data->max_var_size;
+ }
+ pa_data = data->next;
+ early_iounmap(data, sizeof(*efi_var_data));
+ }
+
set_bit(EFI_SYSTEM_TABLES, &x86_efi_facility);

/*
diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c
index bf15d81..684a118 100644
--- a/drivers/firmware/efivars.c
+++ b/drivers/firmware/efivars.c
@@ -85,6 +85,7 @@
#include <linux/ramfs.h>
#include <linux/pagemap.h>

+#include <asm/efi.h>
#include <asm/uaccess.h>

#define EFIVARS_VERSION "0.08"
@@ -139,6 +140,8 @@ struct efivar_attribute {

static struct efivars __efivars;
static struct efivar_operations ops;
+static u64 boot_var_size;
+static u64 boot_used_size;

#define PSTORE_EFI_ATTRIBUTES \
(EFI_VARIABLE_NON_VOLATILE | \
@@ -2002,6 +2005,9 @@ int register_efivars(struct efivars *efivars,
efi_char16_t *variable_name;
unsigned long variable_name_size = 1024;
int error = 0;
+ struct efivar_entry *entry;
+ struct efi_variable *var;
+ u64 active_size = 0;

variable_name = kzalloc(variable_name_size, GFP_KERNEL);
if (!variable_name) {
@@ -2074,6 +2080,25 @@ int register_efivars(struct efivars *efivars,
}
} while (status != EFI_NOT_FOUND);

+ if (boot_used_size) {
+ list_for_each_entry(entry, &efivars->list, list) {
+ var = &entry->var;
+ status = get_var_data(efivars, var);
+
+ if (status ||
+ !(var->Attributes & EFI_VARIABLE_NON_VOLATILE))
+ continue;
+
+ active_size += var->DataSize;
+ active_size += utf16_strsize(var->VariableName, 1024);
+ }
+
+ if (active_size < boot_used_size)
+ boot_var_size = boot_used_size - active_size;
+ else
+ printk(KERN_WARNING FW_BUG "efivars: Inconsistent initial sizes\n");
+ }
+
error = create_efivars_bin_attributes(efivars);
if (error)
unregister_efivars(efivars);
@@ -2121,6 +2146,10 @@ efivars_init(void)
ops.get_next_variable = efi.get_next_variable;
ops.query_variable_store = efi_query_variable_store;

+#ifdef CONFIG_X86
+ boot_used_size = efi_var_store_size - efi_var_remaining_size;
+#endif
+
error = register_efivars(&__efivars, &ops, efi_kobj);
if (error)
goto err_put;
--
1.8.1.2


2013-04-10 02:42:16

by Matthew Garrett

[permalink] [raw]
Subject: [PATCH 2/3] Revert "x86, efivars: firmware bug workarounds should be in platform code"

This reverts commit a6e4d5a03e9e3587e88aba687d8f225f4f04c792. Doing this
workaround properly requires us to work within the variable code.

Signed-off-by: Matthew Garrett <[email protected]>
---
arch/x86/platform/efi/efi.c | 25 -------------------------
drivers/firmware/efivars.c | 18 +++++++++++++++---
include/linux/efi.h | 9 +--------
3 files changed, 16 insertions(+), 36 deletions(-)

diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c
index 659da48..fdc5074 100644
--- a/arch/x86/platform/efi/efi.c
+++ b/arch/x86/platform/efi/efi.c
@@ -1023,28 +1023,3 @@ u64 efi_mem_attributes(unsigned long phys_addr)
}
return 0;
}
-
-/*
- * Some firmware has serious problems when using more than 50% of the EFI
- * variable store, i.e. it triggers bugs that can brick machines. Ensure that
- * we never use more than this safe limit.
- *
- * Return EFI_SUCCESS if it is safe to write 'size' bytes to the variable
- * store.
- */
-efi_status_t efi_query_variable_store(u32 attributes, unsigned long size)
-{
- efi_status_t status;
- u64 storage_size, remaining_size, max_size;
-
- status = efi.query_variable_info(attributes, &storage_size,
- &remaining_size, &max_size);
- if (status != EFI_SUCCESS)
- return status;
-
- if (!storage_size || size > remaining_size || size > max_size ||
- (remaining_size - size) < (storage_size / 2))
- return EFI_OUT_OF_RESOURCES;
-
- return EFI_SUCCESS;
-}
diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c
index 684a118..60e7d8f 100644
--- a/drivers/firmware/efivars.c
+++ b/drivers/firmware/efivars.c
@@ -439,12 +439,24 @@ static efi_status_t
check_var_size_locked(struct efivars *efivars, u32 attributes,
unsigned long size)
{
+ u64 storage_size, remaining_size, max_size;
+ efi_status_t status;
const struct efivar_operations *fops = efivars->ops;

- if (!efivars->ops->query_variable_store)
+ if (!efivars->ops->query_variable_info)
return EFI_UNSUPPORTED;

- return fops->query_variable_store(attributes, size);
+ status = fops->query_variable_info(attributes, &storage_size,
+ &remaining_size, &max_size);
+
+ if (status != EFI_SUCCESS)
+ return status;
+
+ if (!storage_size || size > remaining_size || size > max_size ||
+ (remaining_size - size) < (storage_size / 2))
+ return EFI_OUT_OF_RESOURCES;
+
+ return status;
}


@@ -2144,7 +2156,7 @@ efivars_init(void)
ops.get_variable = efi.get_variable;
ops.set_variable = efi.set_variable;
ops.get_next_variable = efi.get_next_variable;
- ops.query_variable_store = efi_query_variable_store;
+ ops.query_variable_info = efi.query_variable_info;

#ifdef CONFIG_X86
boot_used_size = efi_var_store_size - efi_var_remaining_size;
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 3d7df3d..9bf2f1f 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -333,7 +333,6 @@ typedef efi_status_t efi_query_capsule_caps_t(efi_capsule_header_t **capsules,
unsigned long count,
u64 *max_size,
int *reset_type);
-typedef efi_status_t efi_query_variable_store_t(u32 attributes, unsigned long size);

/*
* EFI Configuration Table and GUID definitions
@@ -576,15 +575,9 @@ extern void efi_enter_virtual_mode (void); /* switch EFI to virtual mode, if pos
#ifdef CONFIG_X86
extern void efi_late_init(void);
extern void efi_free_boot_services(void);
-extern efi_status_t efi_query_variable_store(u32 attributes, unsigned long size);
#else
static inline void efi_late_init(void) {}
static inline void efi_free_boot_services(void) {}
-
-static inline efi_status_t efi_query_variable_store(u32 attributes, unsigned long size)
-{
- return EFI_SUCCESS;
-}
#endif
extern void __iomem *efi_lookup_mapped_addr(u64 phys_addr);
extern u64 efi_get_iobase (void);
@@ -738,7 +731,7 @@ struct efivar_operations {
efi_get_variable_t *get_variable;
efi_get_next_variable_t *get_next_variable;
efi_set_variable_t *set_variable;
- efi_query_variable_store_t *query_variable_store;
+ efi_query_variable_info_t *query_variable_info;
};

struct efivars {
--
1.8.1.2

2013-04-10 02:42:15

by Matthew Garrett

[permalink] [raw]
Subject: [PATCH 3/3] efi: Distinguish between "remaining space" and actually used space

EFI implementations distinguish between space that is actively used by a
variable and space that merely hasn't been garbage collected yet. Space
that hasn't yet been garbage collected isn't available for use and so isn't
counted in the remaining_space field returned by QueryVariableInfo().

Combined with commit 68d9298 this can cause problems. Some implementations
don't garbage collect until the remaining space is smaller than the maximum
variable size, and as a result check_var_size() will always fail once more
than 50% of the variable store has been used even if most of that space is
marked as available for garbage collection. The user is unable to create
new variables, and deleting variables doesn't increase the remaining space.

The problem that 68d9298 was attempting to avoid was one where certain
platforms fail if the actively used space is greater than 50% of the
available storage space. We should be able to calculate that by simply
summing the size of each available variable and subtracting that from
the total storage space. With luck this will fix the problem described in
https://bugzilla.kernel.org/show_bug.cgi?id=55471 without permitting
damage to occur to the machines 68d9298 was attempting to fix.

Signed-off-by: Matthew Garrett <[email protected]>
---
drivers/firmware/efivars.c | 104 +++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 101 insertions(+), 3 deletions(-)

diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c
index 60e7d8f..f5a4e87 100644
--- a/drivers/firmware/efivars.c
+++ b/drivers/firmware/efivars.c
@@ -104,6 +104,13 @@ MODULE_VERSION(EFIVARS_VERSION);
*/
#define GUID_LEN 36

+/*
+ * There's some additional metadata associated with each
+ * variable. Intel's reference implementation is 60 bytes - bump that
+ * to account for potential alignment constraints
+ */
+#define VAR_METADATA_SIZE 64
+
static bool efivars_pstore_disable =
IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE);

@@ -405,6 +412,52 @@ validate_var(struct efi_variable *var, u8 *data, unsigned long len)
}

static efi_status_t
+get_var_size_locked(struct efivars *efivars, struct efi_variable *var)
+{
+ efi_status_t status;
+ void *dummy;
+
+ var->DataSize = 0;
+ status = efivars->ops->get_variable(var->VariableName,
+ &var->VendorGuid,
+ &var->Attributes,
+ &var->DataSize,
+ var->Data);
+
+ if (status != EFI_BUFFER_TOO_SMALL)
+ return status;
+
+ dummy = kmalloc(var->DataSize, GFP_ATOMIC);
+
+ status = efivars->ops->get_variable(var->VariableName,
+ &var->VendorGuid,
+ &var->Attributes,
+ &var->DataSize,
+ dummy);
+
+ kfree(dummy);
+
+ return status;
+}
+
+static efi_status_t
+get_var_size(struct efivars *efivars, struct efi_variable *var)
+{
+ efi_status_t status;
+ unsigned long flags;
+
+ spin_lock_irqsave(&efivars->lock, flags);
+ status = get_var_size_locked(efivars, var);
+ spin_unlock_irqrestore(&efivars->lock, flags);
+
+ if (status != EFI_SUCCESS) {
+ printk(KERN_WARNING "efivars: Failed to get var size 0x%lx!\n",
+ status);
+ }
+ return status;
+}
+
+static efi_status_t
get_var_data_locked(struct efivars *efivars, struct efi_variable *var)
{
efi_status_t status;
@@ -415,6 +468,10 @@ get_var_data_locked(struct efivars *efivars, struct efi_variable *var)
&var->Attributes,
&var->DataSize,
var->Data);
+
+ if (status != EFI_SUCCESS)
+ var->DataSize = 0;
+
return status;
}

@@ -440,8 +497,18 @@ check_var_size_locked(struct efivars *efivars, u32 attributes,
unsigned long size)
{
u64 storage_size, remaining_size, max_size;
+ struct efivar_entry *entry;
+ struct efi_variable *var;
efi_status_t status;
const struct efivar_operations *fops = efivars->ops;
+ unsigned long active_size = 0;
+
+ /*
+ * Any writes other than EFI_VARIABLE_NON_VOLATILE will only hit
+ * RAM, not flash, so ignore them.
+ */
+ if (!(attributes & EFI_VARIABLE_NON_VOLATILE))
+ return EFI_SUCCESS;

if (!efivars->ops->query_variable_info)
return EFI_UNSUPPORTED;
@@ -452,8 +519,39 @@ check_var_size_locked(struct efivars *efivars, u32 attributes,
if (status != EFI_SUCCESS)
return status;

- if (!storage_size || size > remaining_size || size > max_size ||
- (remaining_size - size) < (storage_size / 2))
+ list_for_each_entry(entry, &efivars->list, list) {
+ var = &entry->var;
+ status = EFI_SUCCESS;
+
+ if (var->DataSize == 0)
+ status = get_var_size_locked(efivars, var);
+
+ if (status || !(var->Attributes & EFI_VARIABLE_NON_VOLATILE))
+ continue;
+
+ active_size += var->DataSize;
+ active_size += utf16_strsize(var->VariableName, 1024);
+ active_size += VAR_METADATA_SIZE;
+ }
+
+ /*
+ * Add on the size of any boot services-only variables
+ */
+ active_size += boot_var_size;
+
+ /*
+ * Some firmware implementations refuse to boot if there's insufficient
+ * space in the variable store. We account for that by refusing the
+ * write if permitting it would reduce the available space to under
+ * 50%. However, some firmware won't reclaim variable space until
+ * after the used (not merely the actively used) space drops below
+ * a threshold. We can approximate that case with the value calculated
+ * above. If both the firmware and our calculations indicate that the
+ * available space would drop below 50%, refuse the write.
+ */
+ if (!storage_size || size > remaining_size ||
+ ((active_size + size + VAR_METADATA_SIZE > storage_size / 2) &&
+ (remaining_size - size - VAR_METADATA_SIZE < storage_size / 2)))
return EFI_OUT_OF_RESOURCES;

return status;
@@ -2095,7 +2193,7 @@ int register_efivars(struct efivars *efivars,
if (boot_used_size) {
list_for_each_entry(entry, &efivars->list, list) {
var = &entry->var;
- status = get_var_data(efivars, var);
+ status = get_var_size(efivars, var);

if (status ||
!(var->Attributes & EFI_VARIABLE_NON_VOLATILE))
--
1.8.1.2

2013-04-10 06:03:08

by Lingzhu Xiang

[permalink] [raw]
Subject: Re: [PATCH 3/3] efi: Distinguish between "remaining space" and actually used space

On 04/10/2013 10:41 AM, Matthew Garrett wrote:
> + if (!storage_size || size > remaining_size ||
> + ((active_size + size + VAR_METADATA_SIZE > storage_size / 2) &&
> + (remaining_size - size - VAR_METADATA_SIZE < storage_size / 2)))

This could overflow.

(u64)32768 - (u64)32768 - VAR_METADATA_SIZE < (u64)65536 / 2 == false

2013-04-10 17:46:23

by Matthew Garrett

[permalink] [raw]
Subject: [PATCH V4 3/3] efi: Distinguish between "remaining space" and actually used space

EFI implementations distinguish between space that is actively used by a
variable and space that merely hasn't been garbage collected yet. Space
that hasn't yet been garbage collected isn't available for use and so isn't
counted in the remaining_space field returned by QueryVariableInfo().

Combined with commit 68d9298 this can cause problems. Some implementations
don't garbage collect until the remaining space is smaller than the maximum
variable size, and as a result check_var_size() will always fail once more
than 50% of the variable store has been used even if most of that space is
marked as available for garbage collection. The user is unable to create
new variables, and deleting variables doesn't increase the remaining space.

The problem that 68d9298 was attempting to avoid was one where certain
platforms fail if the actively used space is greater than 50% of the
available storage space. We should be able to calculate that by simply
summing the size of each available variable and subtracting that from
the total storage space. With luck this will fix the problem described in
https://bugzilla.kernel.org/show_bug.cgi?id=55471 without permitting
damage to occur to the machines 68d9298 was attempting to fix.

Signed-off-by: Matthew Garrett <[email protected]>
---
drivers/firmware/efivars.c | 104 +++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 101 insertions(+), 3 deletions(-)

diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c
index 60e7d8f..33a2be0 100644
--- a/drivers/firmware/efivars.c
+++ b/drivers/firmware/efivars.c
@@ -104,6 +104,13 @@ MODULE_VERSION(EFIVARS_VERSION);
*/
#define GUID_LEN 36

+/*
+ * There's some additional metadata associated with each
+ * variable. Intel's reference implementation is 60 bytes - bump that
+ * to account for potential alignment constraints
+ */
+#define VAR_METADATA_SIZE 64
+
static bool efivars_pstore_disable =
IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE);

@@ -405,6 +412,52 @@ validate_var(struct efi_variable *var, u8 *data, unsigned long len)
}

static efi_status_t
+get_var_size_locked(struct efivars *efivars, struct efi_variable *var)
+{
+ efi_status_t status;
+ void *dummy;
+
+ var->DataSize = 0;
+ status = efivars->ops->get_variable(var->VariableName,
+ &var->VendorGuid,
+ &var->Attributes,
+ &var->DataSize,
+ var->Data);
+
+ if (status != EFI_BUFFER_TOO_SMALL)
+ return status;
+
+ dummy = kmalloc(var->DataSize, GFP_ATOMIC);
+
+ status = efivars->ops->get_variable(var->VariableName,
+ &var->VendorGuid,
+ &var->Attributes,
+ &var->DataSize,
+ dummy);
+
+ kfree(dummy);
+
+ return status;
+}
+
+static efi_status_t
+get_var_size(struct efivars *efivars, struct efi_variable *var)
+{
+ efi_status_t status;
+ unsigned long flags;
+
+ spin_lock_irqsave(&efivars->lock, flags);
+ status = get_var_size_locked(efivars, var);
+ spin_unlock_irqrestore(&efivars->lock, flags);
+
+ if (status != EFI_SUCCESS) {
+ printk(KERN_WARNING "efivars: Failed to get var size 0x%lx!\n",
+ status);
+ }
+ return status;
+}
+
+static efi_status_t
get_var_data_locked(struct efivars *efivars, struct efi_variable *var)
{
efi_status_t status;
@@ -415,6 +468,10 @@ get_var_data_locked(struct efivars *efivars, struct efi_variable *var)
&var->Attributes,
&var->DataSize,
var->Data);
+
+ if (status != EFI_SUCCESS)
+ var->DataSize = 0;
+
return status;
}

@@ -440,8 +497,18 @@ check_var_size_locked(struct efivars *efivars, u32 attributes,
unsigned long size)
{
u64 storage_size, remaining_size, max_size;
+ struct efivar_entry *entry;
+ struct efi_variable *var;
efi_status_t status;
const struct efivar_operations *fops = efivars->ops;
+ unsigned long active_size = 0;
+
+ /*
+ * Any writes other than EFI_VARIABLE_NON_VOLATILE will only hit
+ * RAM, not flash, so ignore them.
+ */
+ if (!(attributes & EFI_VARIABLE_NON_VOLATILE))
+ return EFI_SUCCESS;

if (!efivars->ops->query_variable_info)
return EFI_UNSUPPORTED;
@@ -452,8 +519,39 @@ check_var_size_locked(struct efivars *efivars, u32 attributes,
if (status != EFI_SUCCESS)
return status;

- if (!storage_size || size > remaining_size || size > max_size ||
- (remaining_size - size) < (storage_size / 2))
+ list_for_each_entry(entry, &efivars->list, list) {
+ var = &entry->var;
+ status = EFI_SUCCESS;
+
+ if (var->DataSize == 0)
+ status = get_var_size_locked(efivars, var);
+
+ if (status || !(var->Attributes & EFI_VARIABLE_NON_VOLATILE))
+ continue;
+
+ active_size += var->DataSize;
+ active_size += utf16_strsize(var->VariableName, 1024);
+ active_size += VAR_METADATA_SIZE;
+ }
+
+ /*
+ * Add on the size of any boot services-only variables
+ */
+ active_size += boot_var_size;
+
+ /*
+ * Some firmware implementations refuse to boot if there's insufficient
+ * space in the variable store. We account for that by refusing the
+ * write if permitting it would reduce the available space to under
+ * 50%. However, some firmware won't reclaim variable space until
+ * after the used (not merely the actively used) space drops below
+ * a threshold. We can approximate that case with the value calculated
+ * above. If both the firmware and our calculations indicate that the
+ * available space would drop below 50%, refuse the write.
+ */
+ if (!storage_size || size > remaining_size ||
+ ((active_size + size + VAR_METADATA_SIZE > storage_size / 2) &&
+ (remaining_size - size < storage_size / 2)))
return EFI_OUT_OF_RESOURCES;

return status;
@@ -2095,7 +2193,7 @@ int register_efivars(struct efivars *efivars,
if (boot_used_size) {
list_for_each_entry(entry, &efivars->list, list) {
var = &entry->var;
- status = get_var_data(efivars, var);
+ status = get_var_size(efivars, var);

if (status ||
!(var->Attributes & EFI_VARIABLE_NON_VOLATILE))
--
1.8.1.2

2013-04-10 17:46:26

by Matthew Garrett

[permalink] [raw]
Subject: [PATCH V4 2/3] Revert "x86, efivars: firmware bug workarounds should be in platform code"

This reverts commit a6e4d5a03e9e3587e88aba687d8f225f4f04c792. Doing this
workaround properly requires us to work within the variable code.

Signed-off-by: Matthew Garrett <[email protected]>
---
arch/x86/platform/efi/efi.c | 25 -------------------------
drivers/firmware/efivars.c | 18 +++++++++++++++---
include/linux/efi.h | 9 +--------
3 files changed, 16 insertions(+), 36 deletions(-)

diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c
index 659da48..fdc5074 100644
--- a/arch/x86/platform/efi/efi.c
+++ b/arch/x86/platform/efi/efi.c
@@ -1023,28 +1023,3 @@ u64 efi_mem_attributes(unsigned long phys_addr)
}
return 0;
}
-
-/*
- * Some firmware has serious problems when using more than 50% of the EFI
- * variable store, i.e. it triggers bugs that can brick machines. Ensure that
- * we never use more than this safe limit.
- *
- * Return EFI_SUCCESS if it is safe to write 'size' bytes to the variable
- * store.
- */
-efi_status_t efi_query_variable_store(u32 attributes, unsigned long size)
-{
- efi_status_t status;
- u64 storage_size, remaining_size, max_size;
-
- status = efi.query_variable_info(attributes, &storage_size,
- &remaining_size, &max_size);
- if (status != EFI_SUCCESS)
- return status;
-
- if (!storage_size || size > remaining_size || size > max_size ||
- (remaining_size - size) < (storage_size / 2))
- return EFI_OUT_OF_RESOURCES;
-
- return EFI_SUCCESS;
-}
diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c
index 684a118..60e7d8f 100644
--- a/drivers/firmware/efivars.c
+++ b/drivers/firmware/efivars.c
@@ -439,12 +439,24 @@ static efi_status_t
check_var_size_locked(struct efivars *efivars, u32 attributes,
unsigned long size)
{
+ u64 storage_size, remaining_size, max_size;
+ efi_status_t status;
const struct efivar_operations *fops = efivars->ops;

- if (!efivars->ops->query_variable_store)
+ if (!efivars->ops->query_variable_info)
return EFI_UNSUPPORTED;

- return fops->query_variable_store(attributes, size);
+ status = fops->query_variable_info(attributes, &storage_size,
+ &remaining_size, &max_size);
+
+ if (status != EFI_SUCCESS)
+ return status;
+
+ if (!storage_size || size > remaining_size || size > max_size ||
+ (remaining_size - size) < (storage_size / 2))
+ return EFI_OUT_OF_RESOURCES;
+
+ return status;
}


@@ -2144,7 +2156,7 @@ efivars_init(void)
ops.get_variable = efi.get_variable;
ops.set_variable = efi.set_variable;
ops.get_next_variable = efi.get_next_variable;
- ops.query_variable_store = efi_query_variable_store;
+ ops.query_variable_info = efi.query_variable_info;

#ifdef CONFIG_X86
boot_used_size = efi_var_store_size - efi_var_remaining_size;
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 3d7df3d..9bf2f1f 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -333,7 +333,6 @@ typedef efi_status_t efi_query_capsule_caps_t(efi_capsule_header_t **capsules,
unsigned long count,
u64 *max_size,
int *reset_type);
-typedef efi_status_t efi_query_variable_store_t(u32 attributes, unsigned long size);

/*
* EFI Configuration Table and GUID definitions
@@ -576,15 +575,9 @@ extern void efi_enter_virtual_mode (void); /* switch EFI to virtual mode, if pos
#ifdef CONFIG_X86
extern void efi_late_init(void);
extern void efi_free_boot_services(void);
-extern efi_status_t efi_query_variable_store(u32 attributes, unsigned long size);
#else
static inline void efi_late_init(void) {}
static inline void efi_free_boot_services(void) {}
-
-static inline efi_status_t efi_query_variable_store(u32 attributes, unsigned long size)
-{
- return EFI_SUCCESS;
-}
#endif
extern void __iomem *efi_lookup_mapped_addr(u64 phys_addr);
extern u64 efi_get_iobase (void);
@@ -738,7 +731,7 @@ struct efivar_operations {
efi_get_variable_t *get_variable;
efi_get_next_variable_t *get_next_variable;
efi_set_variable_t *set_variable;
- efi_query_variable_store_t *query_variable_store;
+ efi_query_variable_info_t *query_variable_info;
};

struct efivars {
--
1.8.1.2

2013-04-10 17:46:58

by Matthew Garrett

[permalink] [raw]
Subject: [PATCH V4 1/3] efi: Determine how much space is used by boot services-only variables

EFI variables can be flagged as being accessible only within boot services.
This makes it awkward for us to figure out how much space they use at
runtime. In theory we could figure this out by simply comparing the results
from QueryVariableInfo() to the space used by all of our variables, but
that fails if the platform doesn't garbage collect on every boot. Thankfully,
calling QueryVariableInfo() while still inside boot services gives a more
reliable answer. This patch passes that information from the EFI boot stub
up to the efivars code, letting us calculate a reasonably accurate value.

Signed-off-by: Matthew Garrett <[email protected]>
---
arch/x86/boot/compressed/eboot.c | 47 +++++++++++++++++++++++++++++++++++
arch/x86/include/asm/efi.h | 10 ++++++++
arch/x86/include/uapi/asm/bootparam.h | 1 +
arch/x86/platform/efi/efi.c | 24 ++++++++++++++++++
drivers/firmware/efivars.c | 29 +++++++++++++++++++++
5 files changed, 111 insertions(+)

diff --git a/arch/x86/boot/compressed/eboot.c b/arch/x86/boot/compressed/eboot.c
index c205035..8615f75 100644
--- a/arch/x86/boot/compressed/eboot.c
+++ b/arch/x86/boot/compressed/eboot.c
@@ -251,6 +251,51 @@ static void find_bits(unsigned long mask, u8 *pos, u8 *size)
*size = len;
}

+static efi_status_t setup_efi_vars(struct boot_params *params)
+{
+ struct setup_data *data;
+ struct efi_var_bootdata *efidata;
+ u64 store_size, remaining_size, var_size;
+ efi_status_t status;
+
+ if (!sys_table->runtime->query_variable_info)
+ return EFI_UNSUPPORTED;
+
+ data = (struct setup_data *)(unsigned long)params->hdr.setup_data;
+
+ while (data && data->next)
+ data = (struct setup_data *)(unsigned long)data->next;
+
+ status = efi_call_phys4(sys_table->runtime->query_variable_info,
+ EFI_VARIABLE_NON_VOLATILE |
+ EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ EFI_VARIABLE_RUNTIME_ACCESS, &store_size,
+ &remaining_size, &var_size);
+
+ if (status != EFI_SUCCESS)
+ return status;
+
+ status = efi_call_phys3(sys_table->boottime->allocate_pool,
+ EFI_LOADER_DATA, sizeof(*efidata), &efidata);
+
+ if (status != EFI_SUCCESS)
+ return status;
+
+ efidata->data.type = SETUP_EFI_VARS;
+ efidata->data.len = sizeof(struct efi_var_bootdata) -
+ sizeof(struct setup_data);
+ efidata->data.next = 0;
+ efidata->store_size = store_size;
+ efidata->remaining_size = remaining_size;
+ efidata->max_var_size = var_size;
+
+ if (data)
+ data->next = (unsigned long)efidata;
+ else
+ params->hdr.setup_data = (unsigned long)efidata;
+
+}
+
static efi_status_t setup_efi_pci(struct boot_params *params)
{
efi_pci_io_protocol *pci;
@@ -1157,6 +1202,8 @@ struct boot_params *efi_main(void *handle, efi_system_table_t *_table,

setup_graphics(boot_params);

+ setup_efi_vars(boot_params);
+
setup_efi_pci(boot_params);

status = efi_call_phys3(sys_table->boottime->allocate_pool,
diff --git a/arch/x86/include/asm/efi.h b/arch/x86/include/asm/efi.h
index 60c89f3..6c3a154 100644
--- a/arch/x86/include/asm/efi.h
+++ b/arch/x86/include/asm/efi.h
@@ -93,6 +93,9 @@ extern void __iomem *efi_ioremap(unsigned long addr, unsigned long size,

#endif /* CONFIG_X86_32 */

+extern u64 efi_var_store_size;
+extern u64 efi_var_remaining_size;
+extern u64 efi_var_max_var_size;
extern int add_efi_memmap;
extern unsigned long x86_efi_facility;
extern void efi_set_executable(efi_memory_desc_t *md, bool executable);
@@ -102,6 +105,13 @@ extern void efi_call_phys_epilog(void);
extern void efi_unmap_memmap(void);
extern void efi_memory_uc(u64 addr, unsigned long size);

+struct efi_var_bootdata {
+ struct setup_data data;
+ u64 store_size;
+ u64 remaining_size;
+ u64 max_var_size;
+};
+
#ifdef CONFIG_EFI

static inline bool efi_is_native(void)
diff --git a/arch/x86/include/uapi/asm/bootparam.h b/arch/x86/include/uapi/asm/bootparam.h
index c15ddaf..0874424 100644
--- a/arch/x86/include/uapi/asm/bootparam.h
+++ b/arch/x86/include/uapi/asm/bootparam.h
@@ -6,6 +6,7 @@
#define SETUP_E820_EXT 1
#define SETUP_DTB 2
#define SETUP_PCI 3
+#define SETUP_EFI_VARS 4

/* ram_size flags */
#define RAMDISK_IMAGE_START_MASK 0x07FF
diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c
index c89c245..659da48 100644
--- a/arch/x86/platform/efi/efi.c
+++ b/arch/x86/platform/efi/efi.c
@@ -71,6 +71,13 @@ static efi_system_table_t efi_systab __initdata;

unsigned long x86_efi_facility;

+u64 efi_var_store_size;
+EXPORT_SYMBOL(efi_var_store_size);
+u64 efi_var_remaining_size;
+EXPORT_SYMBOL(efi_var_remaining_size);
+u64 efi_var_max_var_size;
+EXPORT_SYMBOL(efi_var_max_var_size);
+
/*
* Returns 1 if 'facility' is enabled, 0 otherwise.
*/
@@ -682,6 +689,9 @@ void __init efi_init(void)
char vendor[100] = "unknown";
int i = 0;
void *tmp;
+ struct setup_data *data;
+ struct efi_var_bootdata *efi_var_data;
+ u64 pa_data;

#ifdef CONFIG_X86_32
if (boot_params.efi_info.efi_systab_hi ||
@@ -699,6 +709,20 @@ void __init efi_init(void)
if (efi_systab_init(efi_phys.systab))
return;

+ pa_data = boot_params.hdr.setup_data;
+ while (pa_data) {
+ data = early_ioremap(pa_data, sizeof(*efi_var_data));
+ if (data->type == SETUP_EFI_VARS) {
+ efi_var_data = (struct efi_var_bootdata *)data;
+
+ efi_var_store_size = efi_var_data->store_size;
+ efi_var_remaining_size = efi_var_data->remaining_size;
+ efi_var_max_var_size = efi_var_data->max_var_size;
+ }
+ pa_data = data->next;
+ early_iounmap(data, sizeof(*efi_var_data));
+ }
+
set_bit(EFI_SYSTEM_TABLES, &x86_efi_facility);

/*
diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c
index bf15d81..684a118 100644
--- a/drivers/firmware/efivars.c
+++ b/drivers/firmware/efivars.c
@@ -85,6 +85,7 @@
#include <linux/ramfs.h>
#include <linux/pagemap.h>

+#include <asm/efi.h>
#include <asm/uaccess.h>

#define EFIVARS_VERSION "0.08"
@@ -139,6 +140,8 @@ struct efivar_attribute {

static struct efivars __efivars;
static struct efivar_operations ops;
+static u64 boot_var_size;
+static u64 boot_used_size;

#define PSTORE_EFI_ATTRIBUTES \
(EFI_VARIABLE_NON_VOLATILE | \
@@ -2002,6 +2005,9 @@ int register_efivars(struct efivars *efivars,
efi_char16_t *variable_name;
unsigned long variable_name_size = 1024;
int error = 0;
+ struct efivar_entry *entry;
+ struct efi_variable *var;
+ u64 active_size = 0;

variable_name = kzalloc(variable_name_size, GFP_KERNEL);
if (!variable_name) {
@@ -2074,6 +2080,25 @@ int register_efivars(struct efivars *efivars,
}
} while (status != EFI_NOT_FOUND);

+ if (boot_used_size) {
+ list_for_each_entry(entry, &efivars->list, list) {
+ var = &entry->var;
+ status = get_var_data(efivars, var);
+
+ if (status ||
+ !(var->Attributes & EFI_VARIABLE_NON_VOLATILE))
+ continue;
+
+ active_size += var->DataSize;
+ active_size += utf16_strsize(var->VariableName, 1024);
+ }
+
+ if (active_size < boot_used_size)
+ boot_var_size = boot_used_size - active_size;
+ else
+ printk(KERN_WARNING FW_BUG "efivars: Inconsistent initial sizes\n");
+ }
+
error = create_efivars_bin_attributes(efivars);
if (error)
unregister_efivars(efivars);
@@ -2121,6 +2146,10 @@ efivars_init(void)
ops.get_next_variable = efi.get_next_variable;
ops.query_variable_store = efi_query_variable_store;

+#ifdef CONFIG_X86
+ boot_used_size = efi_var_store_size - efi_var_remaining_size;
+#endif
+
error = register_efivars(&__efivars, &ops, efi_kobj);
if (error)
goto err_put;
--
1.8.1.2

2013-04-11 13:24:19

by Matt Fleming

[permalink] [raw]
Subject: Re: [PATCH V4 2/3] Revert "x86, efivars: firmware bug workarounds should be in platform code"

On 10/04/13 18:46, Matthew Garrett wrote:
> This reverts commit a6e4d5a03e9e3587e88aba687d8f225f4f04c792. Doing this
> workaround properly requires us to work within the variable code.
>
> Signed-off-by: Matthew Garrett <[email protected]>
> ---
> arch/x86/platform/efi/efi.c | 25 -------------------------
> drivers/firmware/efivars.c | 18 +++++++++++++++---
> include/linux/efi.h | 9 +--------
> 3 files changed, 16 insertions(+), 36 deletions(-)

Does it really? Why can't you just hook into the get_next_variable() and
set_variable() functions in arch/x86/platform/efi/efi.c?

--
Matt Fleming, Intel Open Source Technology Center

2013-04-11 13:30:44

by Matthew Garrett

[permalink] [raw]
Subject: Re: [PATCH V4 2/3] Revert "x86, efivars: firmware bug workarounds should be in platform code"

On Thu, 2013-04-11 at 14:24 +0100, Matt Fleming wrote:
> On 10/04/13 18:46, Matthew Garrett wrote:
> > This reverts commit a6e4d5a03e9e3587e88aba687d8f225f4f04c792. Doing this
> > workaround properly requires us to work within the variable code.
> >
> > Signed-off-by: Matthew Garrett <[email protected]>
> > ---
> > arch/x86/platform/efi/efi.c | 25 -------------------------
> > drivers/firmware/efivars.c | 18 +++++++++++++++---
> > include/linux/efi.h | 9 +--------
> > 3 files changed, 16 insertions(+), 36 deletions(-)
>
> Does it really? Why can't you just hook into the get_next_variable() and
> set_variable() functions in arch/x86/platform/efi/efi.c?

struct efi_variable isn't exported from efivars.c, and chunks of the
workaround need to be called at efivars init anyway.

--
Matthew Garrett | [email protected]
????{.n?+???????+%?????ݶ??w??{.n?+????{??G?????{ay?ʇڙ?,j??f???h?????????z_??(?階?ݢj"???m??????G????????????&???~???iO???z??v?^?m???? ????????I?

2013-04-12 10:16:20

by Lingzhu Xiang

[permalink] [raw]
Subject: Re: [PATCH V4 1/3] efi: Determine how much space is used by boot services-only variables

On 04/11/2013 01:46 AM, Matthew Garrett wrote:
> ops.query_variable_store = efi_query_variable_store;

Can't apply here. ops.query_variable_info = efi.query_variable_info?

2013-04-12 10:22:54

by Matt Fleming

[permalink] [raw]
Subject: Re: [PATCH V4 1/3] efi: Determine how much space is used by boot services-only variables

On 12/04/13 11:16, Lingzhu Xiang wrote:
> On 04/11/2013 01:46 AM, Matthew Garrett wrote:
>> ops.query_variable_store = efi_query_variable_store;
>
> Can't apply here. ops.query_variable_info = efi.query_variable_info?

It's against the 'urgent' branch at,

git://git.kernel.org/pub/scm/linux/kernel/git/mfleming/efi.git

2013-04-12 12:19:25

by Lingzhu Xiang

[permalink] [raw]
Subject: Re: [PATCH V4 1/3] efi: Determine how much space is used by boot services-only variables

On 04/11/2013 01:46 AM, Matthew Garrett wrote:
> EFI variables can be flagged as being accessible only within boot services.
> This makes it awkward for us to figure out how much space they use at
> runtime. In theory we could figure this out by simply comparing the results
> from QueryVariableInfo() to the space used by all of our variables, but
> that fails if the platform doesn't garbage collect on every boot. Thankfully,
> calling QueryVariableInfo() while still inside boot services gives a more
> reliable answer. This patch passes that information from the EFI boot stub
> up to the efivars code, letting us calculate a reasonably accurate value.
>
> Signed-off-by: Matthew Garrett <[email protected]>

I just tried this 3.9-rc6 with this patchset with pstore fulling up
space and various attempts to manually fulling up space. So far I've
been able to delete variables and continue creating variables.

Since QueryVariableInfo is no longer trusted and the accounting is done
by the kernel, I'm somewhat concerned that variables can be repeatedly
created and deleted until nvram is full of garbage to collect and the
firmware hits EFI_OUT_OF_RESOURCES. Could this be any kind of problem?


Lingzhu Xiang

2013-04-15 15:54:24

by Matthew Garrett

[permalink] [raw]
Subject: [PATCH V5 2/2] efi: Distinguish between "remaining space" and actually used space

EFI implementations distinguish between space that is actively used by a
variable and space that merely hasn't been garbage collected yet. Space
that hasn't yet been garbage collected isn't available for use and so isn't
counted in the remaining_space field returned by QueryVariableInfo().

Combined with commit 68d9298 this can cause problems. Some implementations
don't garbage collect until the remaining space is smaller than the maximum
variable size, and as a result check_var_size() will always fail once more
than 50% of the variable store has been used even if most of that space is
marked as available for garbage collection. The user is unable to create
new variables, and deleting variables doesn't increase the remaining space.

The problem that 68d9298 was attempting to avoid was one where certain
platforms fail if the actively used space is greater than 50% of the
available storage space. We should be able to calculate that by simply
summing the size of each available variable and subtracting that from
the total storage space. With luck this will fix the problem described in
https://bugzilla.kernel.org/show_bug.cgi?id=55471 without permitting
damage to occur to the machines 68d9298 was attempting to fix.

Signed-off-by: Matthew Garrett <[email protected]>
---
arch/x86/platform/efi/efi.c | 109 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 102 insertions(+), 7 deletions(-)

diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c
index e844d82..a3f03cd 100644
--- a/arch/x86/platform/efi/efi.c
+++ b/arch/x86/platform/efi/efi.c
@@ -41,6 +41,7 @@
#include <linux/io.h>
#include <linux/reboot.h>
#include <linux/bcd.h>
+#include <linux/ucs2_string.h>

#include <asm/setup.h>
#include <asm/efi.h>
@@ -51,6 +52,13 @@

#define EFI_DEBUG 1

+/*
+ * There's some additional metadata associated with each
+ * variable. Intel's reference implementation is 60 bytes - bump that
+ * to account for potential alignment constraints
+ */
+#define VAR_METADATA_SIZE 64
+
struct efi __read_mostly efi = {
.mps = EFI_INVALID_TABLE_ADDR,
.acpi = EFI_INVALID_TABLE_ADDR,
@@ -72,6 +80,9 @@ static efi_system_table_t efi_systab __initdata;
static u64 efi_var_store_size;
static u64 efi_var_remaining_size;
static u64 efi_var_max_var_size;
+static u64 boot_used_size;
+static u64 boot_var_size;
+static u64 active_size;

unsigned long x86_efi_facility;

@@ -166,8 +177,53 @@ static efi_status_t virt_efi_get_next_variable(unsigned long *name_size,
efi_char16_t *name,
efi_guid_t *vendor)
{
- return efi_call_virt3(get_next_variable,
- name_size, name, vendor);
+ efi_status_t status;
+ static bool finished = false;
+ static u64 var_size;
+
+ status = efi_call_virt3(get_next_variable,
+ name_size, name, vendor);
+
+ if (status == EFI_NOT_FOUND) {
+ finished = true;
+ if (var_size < boot_used_size) {
+ boot_var_size = boot_used_size - var_size;
+ active_size += boot_var_size;
+ } else {
+ printk(KERN_WARNING FW_BUG "efi: Inconsistent initial sizes\n");
+ }
+ }
+
+ if (boot_used_size && !finished) {
+ unsigned long size;
+ u32 attr;
+ efi_status_t s;
+ void *tmp;
+
+ s = virt_efi_get_variable(name, vendor, &attr, &size, NULL);
+
+ if (s != EFI_BUFFER_TOO_SMALL || !size)
+ return status;
+
+ tmp = kmalloc(size, GFP_ATOMIC);
+
+ if (!tmp)
+ return status;
+
+ s = virt_efi_get_variable(name, vendor, &attr, &size, tmp);
+
+ if (s == EFI_SUCCESS && (attr & EFI_VARIABLE_NON_VOLATILE)) {
+ var_size += size;
+ var_size += ucs2_strsize(name, 1024);
+ active_size += size;
+ active_size += VAR_METADATA_SIZE;
+ active_size += ucs2_strsize(name, 1024);
+ }
+
+ kfree(tmp);
+ }
+
+ return status;
}

static efi_status_t virt_efi_set_variable(efi_char16_t *name,
@@ -176,9 +232,34 @@ static efi_status_t virt_efi_set_variable(efi_char16_t *name,
unsigned long data_size,
void *data)
{
- return efi_call_virt5(set_variable,
- name, vendor, attr,
- data_size, data);
+ efi_status_t status;
+ u32 orig_attr = 0;
+ unsigned long orig_size = 0;
+
+ status = virt_efi_get_variable(name, vendor, &orig_attr, &orig_size,
+ NULL);
+
+ if (status != EFI_BUFFER_TOO_SMALL)
+ orig_size = 0;
+
+ status = efi_call_virt5(set_variable,
+ name, vendor, attr,
+ data_size, data);
+
+ if (status == EFI_SUCCESS) {
+ if (orig_size) {
+ active_size -= orig_size;
+ active_size -= ucs2_strsize(name, 1024);
+ active_size -= VAR_METADATA_SIZE;
+ }
+ if (data_size) {
+ active_size += data_size;
+ active_size += ucs2_strsize(name, 1024);
+ active_size += VAR_METADATA_SIZE;
+ }
+ }
+
+ return status;
}

static efi_status_t virt_efi_query_variable_info(u32 attr,
@@ -720,6 +801,8 @@ void __init efi_init(void)
early_iounmap(data, sizeof(*efi_var_data));
}

+ boot_used_size = efi_var_store_size - efi_var_remaining_size;
+
set_bit(EFI_SYSTEM_TABLES, &x86_efi_facility);

/*
@@ -1039,8 +1122,20 @@ efi_status_t efi_query_variable_store(u32 attributes, unsigned long size)
if (status != EFI_SUCCESS)
return status;

- if (!storage_size || size > remaining_size || size > max_size ||
- (remaining_size - size) < (storage_size / 2))
+ /*
+ * Some firmware implementations refuse to boot if there's insufficient
+ * space in the variable store. We account for that by refusing the
+ * write if permitting it would reduce the available space to under
+ * 50%. However, some firmware won't reclaim variable space until
+ * after the used (not merely the actively used) space drops below
+ * a threshold. We can approximate that case with the value calculated
+ * above. If both the firmware and our calculations indicate that the
+ * available space would drop below 50%, refuse the write.
+ */
+
+ if (!storage_size || size > remaining_size ||
+ ((active_size + size + VAR_METADATA_SIZE > storage_size / 2) &&
+ (remaining_size - size < storage_size / 2)))
return EFI_OUT_OF_RESOURCES;

return EFI_SUCCESS;
--
1.8.1.2

2013-04-15 15:54:25

by Matthew Garrett

[permalink] [raw]
Subject: [PATCH V5 1/2] efi: Pass boot services variable info to runtime code

EFI variables can be flagged as being accessible only within boot services.
This makes it awkward for us to figure out how much space they use at
runtime. In theory we could figure this out by simply comparing the results
from QueryVariableInfo() to the space used by all of our variables, but
that fails if the platform doesn't garbage collect on every boot. Thankfully,
calling QueryVariableInfo() while still inside boot services gives a more
reliable answer. This patch passes that information from the EFI boot stub
up to the efi platform code.

Signed-off-by: Matthew Garrett <[email protected]>
---
arch/x86/boot/compressed/eboot.c | 47 +++++++++++++++++++++++++++++++++++
arch/x86/include/asm/efi.h | 7 ++++++
arch/x86/include/uapi/asm/bootparam.h | 1 +
arch/x86/platform/efi/efi.c | 21 ++++++++++++++++
4 files changed, 76 insertions(+)

diff --git a/arch/x86/boot/compressed/eboot.c b/arch/x86/boot/compressed/eboot.c
index c205035..8615f75 100644
--- a/arch/x86/boot/compressed/eboot.c
+++ b/arch/x86/boot/compressed/eboot.c
@@ -251,6 +251,51 @@ static void find_bits(unsigned long mask, u8 *pos, u8 *size)
*size = len;
}

+static efi_status_t setup_efi_vars(struct boot_params *params)
+{
+ struct setup_data *data;
+ struct efi_var_bootdata *efidata;
+ u64 store_size, remaining_size, var_size;
+ efi_status_t status;
+
+ if (!sys_table->runtime->query_variable_info)
+ return EFI_UNSUPPORTED;
+
+ data = (struct setup_data *)(unsigned long)params->hdr.setup_data;
+
+ while (data && data->next)
+ data = (struct setup_data *)(unsigned long)data->next;
+
+ status = efi_call_phys4(sys_table->runtime->query_variable_info,
+ EFI_VARIABLE_NON_VOLATILE |
+ EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ EFI_VARIABLE_RUNTIME_ACCESS, &store_size,
+ &remaining_size, &var_size);
+
+ if (status != EFI_SUCCESS)
+ return status;
+
+ status = efi_call_phys3(sys_table->boottime->allocate_pool,
+ EFI_LOADER_DATA, sizeof(*efidata), &efidata);
+
+ if (status != EFI_SUCCESS)
+ return status;
+
+ efidata->data.type = SETUP_EFI_VARS;
+ efidata->data.len = sizeof(struct efi_var_bootdata) -
+ sizeof(struct setup_data);
+ efidata->data.next = 0;
+ efidata->store_size = store_size;
+ efidata->remaining_size = remaining_size;
+ efidata->max_var_size = var_size;
+
+ if (data)
+ data->next = (unsigned long)efidata;
+ else
+ params->hdr.setup_data = (unsigned long)efidata;
+
+}
+
static efi_status_t setup_efi_pci(struct boot_params *params)
{
efi_pci_io_protocol *pci;
@@ -1157,6 +1202,8 @@ struct boot_params *efi_main(void *handle, efi_system_table_t *_table,

setup_graphics(boot_params);

+ setup_efi_vars(boot_params);
+
setup_efi_pci(boot_params);

status = efi_call_phys3(sys_table->boottime->allocate_pool,
diff --git a/arch/x86/include/asm/efi.h b/arch/x86/include/asm/efi.h
index 60c89f3..2fb5d58 100644
--- a/arch/x86/include/asm/efi.h
+++ b/arch/x86/include/asm/efi.h
@@ -102,6 +102,13 @@ extern void efi_call_phys_epilog(void);
extern void efi_unmap_memmap(void);
extern void efi_memory_uc(u64 addr, unsigned long size);

+struct efi_var_bootdata {
+ struct setup_data data;
+ u64 store_size;
+ u64 remaining_size;
+ u64 max_var_size;
+};
+
#ifdef CONFIG_EFI

static inline bool efi_is_native(void)
diff --git a/arch/x86/include/uapi/asm/bootparam.h b/arch/x86/include/uapi/asm/bootparam.h
index c15ddaf..0874424 100644
--- a/arch/x86/include/uapi/asm/bootparam.h
+++ b/arch/x86/include/uapi/asm/bootparam.h
@@ -6,6 +6,7 @@
#define SETUP_E820_EXT 1
#define SETUP_DTB 2
#define SETUP_PCI 3
+#define SETUP_EFI_VARS 4

/* ram_size flags */
#define RAMDISK_IMAGE_START_MASK 0x07FF
diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c
index c89c245..e844d82 100644
--- a/arch/x86/platform/efi/efi.c
+++ b/arch/x86/platform/efi/efi.c
@@ -69,6 +69,10 @@ struct efi_memory_map memmap;
static struct efi efi_phys __initdata;
static efi_system_table_t efi_systab __initdata;

+static u64 efi_var_store_size;
+static u64 efi_var_remaining_size;
+static u64 efi_var_max_var_size;
+
unsigned long x86_efi_facility;

/*
@@ -682,6 +686,9 @@ void __init efi_init(void)
char vendor[100] = "unknown";
int i = 0;
void *tmp;
+ struct setup_data *data;
+ struct efi_var_bootdata *efi_var_data;
+ u64 pa_data;

#ifdef CONFIG_X86_32
if (boot_params.efi_info.efi_systab_hi ||
@@ -699,6 +706,20 @@ void __init efi_init(void)
if (efi_systab_init(efi_phys.systab))
return;

+ pa_data = boot_params.hdr.setup_data;
+ while (pa_data) {
+ data = early_ioremap(pa_data, sizeof(*efi_var_data));
+ if (data->type == SETUP_EFI_VARS) {
+ efi_var_data = (struct efi_var_bootdata *)data;
+
+ efi_var_store_size = efi_var_data->store_size;
+ efi_var_remaining_size = efi_var_data->remaining_size;
+ efi_var_max_var_size = efi_var_data->max_var_size;
+ }
+ pa_data = data->next;
+ early_iounmap(data, sizeof(*efi_var_data));
+ }
+
set_bit(EFI_SYSTEM_TABLES, &x86_efi_facility);

/*
--
1.8.1.2

2013-04-15 20:10:10

by Matthew Garrett

[permalink] [raw]
Subject: [PATCH V6 3/3] efi: Distinguish between "remaining space" and actually used space

EFI implementations distinguish between space that is actively used by a
variable and space that merely hasn't been garbage collected yet. Space
that hasn't yet been garbage collected isn't available for use and so isn't
counted in the remaining_space field returned by QueryVariableInfo().

Combined with commit 68d9298 this can cause problems. Some implementations
don't garbage collect until the remaining space is smaller than the maximum
variable size, and as a result check_var_size() will always fail once more
than 50% of the variable store has been used even if most of that space is
marked as available for garbage collection. The user is unable to create
new variables, and deleting variables doesn't increase the remaining space.

The problem that 68d9298 was attempting to avoid was one where certain
platforms fail if the actively used space is greater than 50% of the
available storage space. We should be able to calculate that by simply
summing the size of each available variable and subtracting that from
the total storage space. With luck this will fix the problem described in
https://bugzilla.kernel.org/show_bug.cgi?id=55471 without permitting
damage to occur to the machines 68d9298 was attempting to fix.

Signed-off-by: Matthew Garrett <[email protected]>
---
arch/x86/platform/efi/efi.c | 109 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 102 insertions(+), 7 deletions(-)

diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c
index e844d82..a3f03cd 100644
--- a/arch/x86/platform/efi/efi.c
+++ b/arch/x86/platform/efi/efi.c
@@ -41,6 +41,7 @@
#include <linux/io.h>
#include <linux/reboot.h>
#include <linux/bcd.h>
+#include <linux/ucs2_string.h>

#include <asm/setup.h>
#include <asm/efi.h>
@@ -51,6 +52,13 @@

#define EFI_DEBUG 1

+/*
+ * There's some additional metadata associated with each
+ * variable. Intel's reference implementation is 60 bytes - bump that
+ * to account for potential alignment constraints
+ */
+#define VAR_METADATA_SIZE 64
+
struct efi __read_mostly efi = {
.mps = EFI_INVALID_TABLE_ADDR,
.acpi = EFI_INVALID_TABLE_ADDR,
@@ -72,6 +80,9 @@ static efi_system_table_t efi_systab __initdata;
static u64 efi_var_store_size;
static u64 efi_var_remaining_size;
static u64 efi_var_max_var_size;
+static u64 boot_used_size;
+static u64 boot_var_size;
+static u64 active_size;

unsigned long x86_efi_facility;

@@ -166,8 +177,53 @@ static efi_status_t virt_efi_get_next_variable(unsigned long *name_size,
efi_char16_t *name,
efi_guid_t *vendor)
{
- return efi_call_virt3(get_next_variable,
- name_size, name, vendor);
+ efi_status_t status;
+ static bool finished = false;
+ static u64 var_size;
+
+ status = efi_call_virt3(get_next_variable,
+ name_size, name, vendor);
+
+ if (status == EFI_NOT_FOUND) {
+ finished = true;
+ if (var_size < boot_used_size) {
+ boot_var_size = boot_used_size - var_size;
+ active_size += boot_var_size;
+ } else {
+ printk(KERN_WARNING FW_BUG "efi: Inconsistent initial sizes\n");
+ }
+ }
+
+ if (boot_used_size && !finished) {
+ unsigned long size;
+ u32 attr;
+ efi_status_t s;
+ void *tmp;
+
+ s = virt_efi_get_variable(name, vendor, &attr, &size, NULL);
+
+ if (s != EFI_BUFFER_TOO_SMALL || !size)
+ return status;
+
+ tmp = kmalloc(size, GFP_ATOMIC);
+
+ if (!tmp)
+ return status;
+
+ s = virt_efi_get_variable(name, vendor, &attr, &size, tmp);
+
+ if (s == EFI_SUCCESS && (attr & EFI_VARIABLE_NON_VOLATILE)) {
+ var_size += size;
+ var_size += ucs2_strsize(name, 1024);
+ active_size += size;
+ active_size += VAR_METADATA_SIZE;
+ active_size += ucs2_strsize(name, 1024);
+ }
+
+ kfree(tmp);
+ }
+
+ return status;
}

static efi_status_t virt_efi_set_variable(efi_char16_t *name,
@@ -176,9 +232,34 @@ static efi_status_t virt_efi_set_variable(efi_char16_t *name,
unsigned long data_size,
void *data)
{
- return efi_call_virt5(set_variable,
- name, vendor, attr,
- data_size, data);
+ efi_status_t status;
+ u32 orig_attr = 0;
+ unsigned long orig_size = 0;
+
+ status = virt_efi_get_variable(name, vendor, &orig_attr, &orig_size,
+ NULL);
+
+ if (status != EFI_BUFFER_TOO_SMALL)
+ orig_size = 0;
+
+ status = efi_call_virt5(set_variable,
+ name, vendor, attr,
+ data_size, data);
+
+ if (status == EFI_SUCCESS) {
+ if (orig_size) {
+ active_size -= orig_size;
+ active_size -= ucs2_strsize(name, 1024);
+ active_size -= VAR_METADATA_SIZE;
+ }
+ if (data_size) {
+ active_size += data_size;
+ active_size += ucs2_strsize(name, 1024);
+ active_size += VAR_METADATA_SIZE;
+ }
+ }
+
+ return status;
}

static efi_status_t virt_efi_query_variable_info(u32 attr,
@@ -720,6 +801,8 @@ void __init efi_init(void)
early_iounmap(data, sizeof(*efi_var_data));
}

+ boot_used_size = efi_var_store_size - efi_var_remaining_size;
+
set_bit(EFI_SYSTEM_TABLES, &x86_efi_facility);

/*
@@ -1039,8 +1122,20 @@ efi_status_t efi_query_variable_store(u32 attributes, unsigned long size)
if (status != EFI_SUCCESS)
return status;

- if (!storage_size || size > remaining_size || size > max_size ||
- (remaining_size - size) < (storage_size / 2))
+ /*
+ * Some firmware implementations refuse to boot if there's insufficient
+ * space in the variable store. We account for that by refusing the
+ * write if permitting it would reduce the available space to under
+ * 50%. However, some firmware won't reclaim variable space until
+ * after the used (not merely the actively used) space drops below
+ * a threshold. We can approximate that case with the value calculated
+ * above. If both the firmware and our calculations indicate that the
+ * available space would drop below 50%, refuse the write.
+ */
+
+ if (!storage_size || size > remaining_size ||
+ ((active_size + size + VAR_METADATA_SIZE > storage_size / 2) &&
+ (remaining_size - size < storage_size / 2)))
return EFI_OUT_OF_RESOURCES;

return EFI_SUCCESS;
--
1.8.1.2

2013-04-15 20:10:08

by Matthew Garrett

[permalink] [raw]
Subject: [PATCH V6 1/3] Move utf16 functions to kernel core and rename

We want to be able to use the utf16 functions that are currently present
in the EFI variables code in platform-specific code as well. Move them to
the kernel core, and in the process rename them to accurately describe what
they do - they don't handle UTF16, only UCS2.

Signed-off-by: Matthew Garrett <[email protected]>
---
drivers/firmware/Kconfig | 1 +
drivers/firmware/efivars.c | 80 ++++++++++-----------------------------------
include/linux/ucs2_string.h | 14 ++++++++
lib/Kconfig | 3 ++
lib/Makefile | 2 ++
lib/ucs2_string.c | 51 +++++++++++++++++++++++++++++
6 files changed, 89 insertions(+), 62 deletions(-)
create mode 100644 include/linux/ucs2_string.h
create mode 100644 lib/ucs2_string.c

diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index 42c759a..3e53200 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -39,6 +39,7 @@ config FIRMWARE_MEMMAP
config EFI_VARS
tristate "EFI Variable Support via sysfs"
depends on EFI
+ select UCS2_STRING
default n
help
If you say Y here, you are able to get EFI (Extensible Firmware
diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c
index bf15d81..182ce94 100644
--- a/drivers/firmware/efivars.c
+++ b/drivers/firmware/efivars.c
@@ -80,6 +80,7 @@
#include <linux/slab.h>
#include <linux/pstore.h>
#include <linux/ctype.h>
+#include <linux/ucs2_string.h>

#include <linux/fs.h>
#include <linux/ramfs.h>
@@ -172,51 +173,6 @@ static void efivar_update_sysfs_entries(struct work_struct *);
static DECLARE_WORK(efivar_work, efivar_update_sysfs_entries);
static bool efivar_wq_enabled = true;

-/* Return the number of unicode characters in data */
-static unsigned long
-utf16_strnlen(efi_char16_t *s, size_t maxlength)
-{
- unsigned long length = 0;
-
- while (*s++ != 0 && length < maxlength)
- length++;
- return length;
-}
-
-static inline unsigned long
-utf16_strlen(efi_char16_t *s)
-{
- return utf16_strnlen(s, ~0UL);
-}
-
-/*
- * Return the number of bytes is the length of this string
- * Note: this is NOT the same as the number of unicode characters
- */
-static inline unsigned long
-utf16_strsize(efi_char16_t *data, unsigned long maxlength)
-{
- return utf16_strnlen(data, maxlength/sizeof(efi_char16_t)) * sizeof(efi_char16_t);
-}
-
-static inline int
-utf16_strncmp(const efi_char16_t *a, const efi_char16_t *b, size_t len)
-{
- while (1) {
- if (len == 0)
- return 0;
- if (*a < *b)
- return -1;
- if (*a > *b)
- return 1;
- if (*a == 0) /* implies *b == 0 */
- return 0;
- a++;
- b++;
- len--;
- }
-}
-
static bool
validate_device_path(struct efi_variable *var, int match, u8 *buffer,
unsigned long len)
@@ -268,7 +224,7 @@ validate_load_option(struct efi_variable *var, int match, u8 *buffer,
u16 filepathlength;
int i, desclength = 0, namelen;

- namelen = utf16_strnlen(var->VariableName, sizeof(var->VariableName));
+ namelen = ucs2_strnlen(var->VariableName, sizeof(var->VariableName));

/* Either "Boot" or "Driver" followed by four digits of hex */
for (i = match; i < match+4; i++) {
@@ -291,7 +247,7 @@ validate_load_option(struct efi_variable *var, int match, u8 *buffer,
* There's no stored length for the description, so it has to be
* found by hand
*/
- desclength = utf16_strsize((efi_char16_t *)(buffer + 6), len - 6) + 2;
+ desclength = ucs2_strsize((efi_char16_t *)(buffer + 6), len - 6) + 2;

/* Each boot entry must have a descriptor */
if (!desclength)
@@ -581,7 +537,7 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
spin_lock_irq(&efivars->lock);

status = check_var_size_locked(efivars, new_var->Attributes,
- new_var->DataSize + utf16_strsize(new_var->VariableName, 1024));
+ new_var->DataSize + ucs2_strsize(new_var->VariableName, 1024));

if (status == EFI_SUCCESS || status == EFI_UNSUPPORTED)
status = efivars->ops->set_variable(new_var->VariableName,
@@ -759,7 +715,7 @@ static ssize_t efivarfs_file_write(struct file *file,
* QueryVariableInfo() isn't supported by the firmware.
*/

- varsize = datasize + utf16_strsize(var->var.VariableName, 1024);
+ varsize = datasize + ucs2_strsize(var->var.VariableName, 1024);
status = check_var_size(efivars, attributes, varsize);

if (status != EFI_SUCCESS) {
@@ -1211,7 +1167,7 @@ static int efivarfs_fill_super(struct super_block *sb, void *data, int silent)

inode = NULL;

- len = utf16_strlen(entry->var.VariableName);
+ len = ucs2_strlen(entry->var.VariableName);

/* name, plus '-', plus GUID, plus NUL*/
name = kmalloc(len + 1 + GUID_LEN + 1, GFP_ATOMIC);
@@ -1469,8 +1425,8 @@ static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count,

if (efi_guidcmp(entry->var.VendorGuid, vendor))
continue;
- if (utf16_strncmp(entry->var.VariableName, efi_name,
- utf16_strlen(efi_name))) {
+ if (ucs2_strncmp(entry->var.VariableName, efi_name,
+ ucs2_strlen(efi_name))) {
/*
* Check if an old format,
* which doesn't support holding
@@ -1482,8 +1438,8 @@ static int efi_pstore_erase(enum pstore_type_id type, u64 id, int count,
for (i = 0; i < DUMP_NAME_LEN; i++)
efi_name_old[i] = name_old[i];

- if (utf16_strncmp(entry->var.VariableName, efi_name_old,
- utf16_strlen(efi_name_old)))
+ if (ucs2_strncmp(entry->var.VariableName, efi_name_old,
+ ucs2_strlen(efi_name_old)))
continue;
}

@@ -1561,8 +1517,8 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
* Does this variable already exist?
*/
list_for_each_entry_safe(search_efivar, n, &efivars->list, list) {
- strsize1 = utf16_strsize(search_efivar->var.VariableName, 1024);
- strsize2 = utf16_strsize(new_var->VariableName, 1024);
+ strsize1 = ucs2_strsize(search_efivar->var.VariableName, 1024);
+ strsize2 = ucs2_strsize(new_var->VariableName, 1024);
if (strsize1 == strsize2 &&
!memcmp(&(search_efivar->var.VariableName),
new_var->VariableName, strsize1) &&
@@ -1578,7 +1534,7 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
}

status = check_var_size_locked(efivars, new_var->Attributes,
- new_var->DataSize + utf16_strsize(new_var->VariableName, 1024));
+ new_var->DataSize + ucs2_strsize(new_var->VariableName, 1024));

if (status && status != EFI_UNSUPPORTED) {
spin_unlock_irq(&efivars->lock);
@@ -1602,7 +1558,7 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,

/* Create the entry in sysfs. Locking is not required here */
status = efivar_create_sysfs_entry(efivars,
- utf16_strsize(new_var->VariableName,
+ ucs2_strsize(new_var->VariableName,
1024),
new_var->VariableName,
&new_var->VendorGuid);
@@ -1632,8 +1588,8 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
* Does this variable already exist?
*/
list_for_each_entry_safe(search_efivar, n, &efivars->list, list) {
- strsize1 = utf16_strsize(search_efivar->var.VariableName, 1024);
- strsize2 = utf16_strsize(del_var->VariableName, 1024);
+ strsize1 = ucs2_strsize(search_efivar->var.VariableName, 1024);
+ strsize2 = ucs2_strsize(del_var->VariableName, 1024);
if (strsize1 == strsize2 &&
!memcmp(&(search_efivar->var.VariableName),
del_var->VariableName, strsize1) &&
@@ -1679,9 +1635,9 @@ static bool variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor)
unsigned long strsize1, strsize2;
bool found = false;

- strsize1 = utf16_strsize(variable_name, 1024);
+ strsize1 = ucs2_strsize(variable_name, 1024);
list_for_each_entry_safe(entry, n, &efivars->list, list) {
- strsize2 = utf16_strsize(entry->var.VariableName, 1024);
+ strsize2 = ucs2_strsize(entry->var.VariableName, 1024);
if (strsize1 == strsize2 &&
!memcmp(variable_name, &(entry->var.VariableName),
strsize2) &&
diff --git a/include/linux/ucs2_string.h b/include/linux/ucs2_string.h
new file mode 100644
index 0000000..cbb20af
--- /dev/null
+++ b/include/linux/ucs2_string.h
@@ -0,0 +1,14 @@
+#ifndef _LINUX_UCS2_STRING_H_
+#define _LINUX_UCS2_STRING_H_
+
+#include <linux/types.h> /* for size_t */
+#include <linux/stddef.h> /* for NULL */
+
+typedef u16 ucs2_char_t;
+
+unsigned long ucs2_strnlen(const ucs2_char_t *s, size_t maxlength);
+unsigned long ucs2_strlen(const ucs2_char_t *s);
+unsigned long ucs2_strsize(const ucs2_char_t *data, unsigned long maxlength);
+int ucs2_strncmp(const ucs2_char_t *a, const ucs2_char_t *b, size_t len);
+
+#endif /* _LINUX_UCS2_STRING_H_ */
diff --git a/lib/Kconfig b/lib/Kconfig
index 3958dc4..fe01d41 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -404,4 +404,7 @@ config OID_REGISTRY
help
Enable fast lookup object identifier registry.

+config UCS2_STRING
+ tristate
+
endmenu
diff --git a/lib/Makefile b/lib/Makefile
index d7946ff..6e2cc56 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -174,3 +174,5 @@ quiet_cmd_build_OID_registry = GEN $@
cmd_build_OID_registry = perl $(srctree)/$(src)/build_OID_registry $< $@

clean-files += oid_registry_data.c
+
+obj-$(CONFIG_UCS2_STRING) += ucs2_string.o
diff --git a/lib/ucs2_string.c b/lib/ucs2_string.c
new file mode 100644
index 0000000..6f500ef
--- /dev/null
+++ b/lib/ucs2_string.c
@@ -0,0 +1,51 @@
+#include <linux/ucs2_string.h>
+#include <linux/module.h>
+
+/* Return the number of unicode characters in data */
+unsigned long
+ucs2_strnlen(const ucs2_char_t *s, size_t maxlength)
+{
+ unsigned long length = 0;
+
+ while (*s++ != 0 && length < maxlength)
+ length++;
+ return length;
+}
+EXPORT_SYMBOL(ucs2_strnlen);
+
+unsigned long
+ucs2_strlen(const ucs2_char_t *s)
+{
+ return ucs2_strnlen(s, ~0UL);
+}
+EXPORT_SYMBOL(ucs2_strlen);
+
+/*
+ * Return the number of bytes is the length of this string
+ * Note: this is NOT the same as the number of unicode characters
+ */
+unsigned long
+ucs2_strsize(const ucs2_char_t *data, unsigned long maxlength)
+{
+ return ucs2_strnlen(data, maxlength/sizeof(ucs2_char_t)) * sizeof(ucs2_char_t);
+}
+EXPORT_SYMBOL(ucs2_strsize);
+
+int
+ucs2_strncmp(const ucs2_char_t *a, const ucs2_char_t *b, size_t len)
+{
+ while (1) {
+ if (len == 0)
+ return 0;
+ if (*a < *b)
+ return -1;
+ if (*a > *b)
+ return 1;
+ if (*a == 0) /* implies *b == 0 */
+ return 0;
+ a++;
+ b++;
+ len--;
+ }
+}
+EXPORT_SYMBOL(ucs2_strncmp);
--
1.8.1.2

2013-04-15 20:10:47

by Matthew Garrett

[permalink] [raw]
Subject: [PATCH V6 2/3] efi: Pass boot services variable info to runtime code

EFI variables can be flagged as being accessible only within boot services.
This makes it awkward for us to figure out how much space they use at
runtime. In theory we could figure this out by simply comparing the results
from QueryVariableInfo() to the space used by all of our variables, but
that fails if the platform doesn't garbage collect on every boot. Thankfully,
calling QueryVariableInfo() while still inside boot services gives a more
reliable answer. This patch passes that information from the EFI boot stub
up to the efi platform code.

Signed-off-by: Matthew Garrett <[email protected]>
---
arch/x86/boot/compressed/eboot.c | 47 +++++++++++++++++++++++++++++++++++
arch/x86/include/asm/efi.h | 7 ++++++
arch/x86/include/uapi/asm/bootparam.h | 1 +
arch/x86/platform/efi/efi.c | 21 ++++++++++++++++
4 files changed, 76 insertions(+)

diff --git a/arch/x86/boot/compressed/eboot.c b/arch/x86/boot/compressed/eboot.c
index c205035..8615f75 100644
--- a/arch/x86/boot/compressed/eboot.c
+++ b/arch/x86/boot/compressed/eboot.c
@@ -251,6 +251,51 @@ static void find_bits(unsigned long mask, u8 *pos, u8 *size)
*size = len;
}

+static efi_status_t setup_efi_vars(struct boot_params *params)
+{
+ struct setup_data *data;
+ struct efi_var_bootdata *efidata;
+ u64 store_size, remaining_size, var_size;
+ efi_status_t status;
+
+ if (!sys_table->runtime->query_variable_info)
+ return EFI_UNSUPPORTED;
+
+ data = (struct setup_data *)(unsigned long)params->hdr.setup_data;
+
+ while (data && data->next)
+ data = (struct setup_data *)(unsigned long)data->next;
+
+ status = efi_call_phys4(sys_table->runtime->query_variable_info,
+ EFI_VARIABLE_NON_VOLATILE |
+ EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ EFI_VARIABLE_RUNTIME_ACCESS, &store_size,
+ &remaining_size, &var_size);
+
+ if (status != EFI_SUCCESS)
+ return status;
+
+ status = efi_call_phys3(sys_table->boottime->allocate_pool,
+ EFI_LOADER_DATA, sizeof(*efidata), &efidata);
+
+ if (status != EFI_SUCCESS)
+ return status;
+
+ efidata->data.type = SETUP_EFI_VARS;
+ efidata->data.len = sizeof(struct efi_var_bootdata) -
+ sizeof(struct setup_data);
+ efidata->data.next = 0;
+ efidata->store_size = store_size;
+ efidata->remaining_size = remaining_size;
+ efidata->max_var_size = var_size;
+
+ if (data)
+ data->next = (unsigned long)efidata;
+ else
+ params->hdr.setup_data = (unsigned long)efidata;
+
+}
+
static efi_status_t setup_efi_pci(struct boot_params *params)
{
efi_pci_io_protocol *pci;
@@ -1157,6 +1202,8 @@ struct boot_params *efi_main(void *handle, efi_system_table_t *_table,

setup_graphics(boot_params);

+ setup_efi_vars(boot_params);
+
setup_efi_pci(boot_params);

status = efi_call_phys3(sys_table->boottime->allocate_pool,
diff --git a/arch/x86/include/asm/efi.h b/arch/x86/include/asm/efi.h
index 60c89f3..2fb5d58 100644
--- a/arch/x86/include/asm/efi.h
+++ b/arch/x86/include/asm/efi.h
@@ -102,6 +102,13 @@ extern void efi_call_phys_epilog(void);
extern void efi_unmap_memmap(void);
extern void efi_memory_uc(u64 addr, unsigned long size);

+struct efi_var_bootdata {
+ struct setup_data data;
+ u64 store_size;
+ u64 remaining_size;
+ u64 max_var_size;
+};
+
#ifdef CONFIG_EFI

static inline bool efi_is_native(void)
diff --git a/arch/x86/include/uapi/asm/bootparam.h b/arch/x86/include/uapi/asm/bootparam.h
index c15ddaf..0874424 100644
--- a/arch/x86/include/uapi/asm/bootparam.h
+++ b/arch/x86/include/uapi/asm/bootparam.h
@@ -6,6 +6,7 @@
#define SETUP_E820_EXT 1
#define SETUP_DTB 2
#define SETUP_PCI 3
+#define SETUP_EFI_VARS 4

/* ram_size flags */
#define RAMDISK_IMAGE_START_MASK 0x07FF
diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c
index c89c245..e844d82 100644
--- a/arch/x86/platform/efi/efi.c
+++ b/arch/x86/platform/efi/efi.c
@@ -69,6 +69,10 @@ struct efi_memory_map memmap;
static struct efi efi_phys __initdata;
static efi_system_table_t efi_systab __initdata;

+static u64 efi_var_store_size;
+static u64 efi_var_remaining_size;
+static u64 efi_var_max_var_size;
+
unsigned long x86_efi_facility;

/*
@@ -682,6 +686,9 @@ void __init efi_init(void)
char vendor[100] = "unknown";
int i = 0;
void *tmp;
+ struct setup_data *data;
+ struct efi_var_bootdata *efi_var_data;
+ u64 pa_data;

#ifdef CONFIG_X86_32
if (boot_params.efi_info.efi_systab_hi ||
@@ -699,6 +706,20 @@ void __init efi_init(void)
if (efi_systab_init(efi_phys.systab))
return;

+ pa_data = boot_params.hdr.setup_data;
+ while (pa_data) {
+ data = early_ioremap(pa_data, sizeof(*efi_var_data));
+ if (data->type == SETUP_EFI_VARS) {
+ efi_var_data = (struct efi_var_bootdata *)data;
+
+ efi_var_store_size = efi_var_data->store_size;
+ efi_var_remaining_size = efi_var_data->remaining_size;
+ efi_var_max_var_size = efi_var_data->max_var_size;
+ }
+ pa_data = data->next;
+ early_iounmap(data, sizeof(*efi_var_data));
+ }
+
set_bit(EFI_SYSTEM_TABLES, &x86_efi_facility);

/*
--
1.8.1.2

2013-04-15 20:10:07

by Matthew Garrett

[permalink] [raw]
Subject: Fix UEFI variable paranoia

Identical to V5, except with the UCS2 helper functions included this time.

2013-04-16 10:15:19

by Matt Fleming

[permalink] [raw]
Subject: Re: Fix UEFI variable paranoia

On 15/04/13 21:09, Matthew Garrett wrote:
> Identical to V5, except with the UCS2 helper functions included this time.

Thanks these looks good to me. I've applied all three patches to the
'urgent' branch.

--
Matt Fleming, Intel Open Source Technology Center

2013-04-16 14:31:17

by Sergey Vlasov

[permalink] [raw]
Subject: [PATCH 1/2] x86/Kconfig: Make EFI select UCS2_STRING

The commit "efi: Distinguish between "remaining space" and actually used
space" added usage of ucs2_*() functions to arch/x86/platform/efi/efi.c,
but the only thing which selected UCS2_STRING was EFI_VARS, which is
technically optional and can be built as a module.

Signed-off-by: Sergey Vlasov <[email protected]>
---
arch/x86/Kconfig | 1 +
1 file changed, 1 insertion(+)

diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index a4f24f5..01af853 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -1549,6 +1549,7 @@ config X86_SMAP
config EFI
bool "EFI runtime service support"
depends on ACPI
+ select UCS2_STRING
---help---
This enables the kernel to use EFI runtime services that are
available (such as the EFI variable services).
--
1.8.1.1

2013-04-16 14:31:25

by Sergey Vlasov

[permalink] [raw]
Subject: [PATCH 2/2] efi: Export efi_query_variable_store() for efivars.ko

Fixes build with CONFIG_EFI_VARS=m which was broken after the commit
"x86, efivars: firmware bug workarounds should be in platform code".

Signed-off-by: Sergey Vlasov <[email protected]>
---
arch/x86/platform/efi/efi.c | 1 +
1 file changed, 1 insertion(+)

diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c
index 4959e3f..4f364c7 100644
--- a/arch/x86/platform/efi/efi.c
+++ b/arch/x86/platform/efi/efi.c
@@ -1144,3 +1144,4 @@ efi_status_t efi_query_variable_store(u32 attributes, unsigned long size)

return EFI_SUCCESS;
}
+EXPORT_SYMBOL_GPL(efi_query_variable_store);
--
1.8.1.1

2013-04-16 16:39:42

by Matt Fleming

[permalink] [raw]
Subject: Re: [PATCH 1/2] x86/Kconfig: Make EFI select UCS2_STRING

On 16/04/13 15:31, Sergey Vlasov wrote:
> The commit "efi: Distinguish between "remaining space" and actually used
> space" added usage of ucs2_*() functions to arch/x86/platform/efi/efi.c,
> but the only thing which selected UCS2_STRING was EFI_VARS, which is
> technically optional and can be built as a module.
>
> Signed-off-by: Sergey Vlasov <[email protected]>
> ---
> arch/x86/Kconfig | 1 +
> 1 file changed, 1 insertion(+)

Applied, thanks!

2013-04-16 16:40:01

by Matt Fleming

[permalink] [raw]
Subject: Re: [PATCH 2/2] efi: Export efi_query_variable_store() for efivars.ko

On 16/04/13 15:31, Sergey Vlasov wrote:
> Fixes build with CONFIG_EFI_VARS=m which was broken after the commit
> "x86, efivars: firmware bug workarounds should be in platform code".
>
> Signed-off-by: Sergey Vlasov <[email protected]>
> ---
> arch/x86/platform/efi/efi.c | 1 +
> 1 file changed, 1 insertion(+)

Applied, thanks!

2013-04-17 10:49:27

by Lingzhu Xiang

[permalink] [raw]
Subject: Re: [PATCH V6 3/3] efi: Distinguish between "remaining space" and actually used space

On 04/16/2013 04:09 AM, Matthew Garrett wrote:
> EFI implementations distinguish between space that is actively used by a
> variable and space that merely hasn't been garbage collected yet. Space
> that hasn't yet been garbage collected isn't available for use and so isn't
> counted in the remaining_space field returned by QueryVariableInfo().
>
> Combined with commit 68d9298 this can cause problems. Some implementations
> don't garbage collect until the remaining space is smaller than the maximum
> variable size, and as a result check_var_size() will always fail once more
> than 50% of the variable store has been used even if most of that space is
> marked as available for garbage collection. The user is unable to create
> new variables, and deleting variables doesn't increase the remaining space.
>
> The problem that 68d9298 was attempting to avoid was one where certain
> platforms fail if the actively used space is greater than 50% of the
> available storage space. We should be able to calculate that by simply
> summing the size of each available variable and subtracting that from
> the total storage space. With luck this will fix the problem described in
> https://bugzilla.kernel.org/show_bug.cgi?id=55471 without permitting
> damage to occur to the machines 68d9298 was attempting to fix.
>
> Signed-off-by: Matthew Garrett <[email protected]>
> ---
> arch/x86/platform/efi/efi.c | 109 +++++++++++++++++++++++++++++++++++++++++---
> 1 file changed, 102 insertions(+), 7 deletions(-)
>
> diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c
> index e844d82..a3f03cd 100644
> --- a/arch/x86/platform/efi/efi.c
> +++ b/arch/x86/platform/efi/efi.c
> @@ -41,6 +41,7 @@
> #include <linux/io.h>
> #include <linux/reboot.h>
> #include <linux/bcd.h>
> +#include <linux/ucs2_string.h>
>
> #include <asm/setup.h>
> #include <asm/efi.h>
> @@ -51,6 +52,13 @@
>
> #define EFI_DEBUG 1
>
> +/*
> + * There's some additional metadata associated with each
> + * variable. Intel's reference implementation is 60 bytes - bump that
> + * to account for potential alignment constraints
> + */
> +#define VAR_METADATA_SIZE 64
> +
> struct efi __read_mostly efi = {
> .mps = EFI_INVALID_TABLE_ADDR,
> .acpi = EFI_INVALID_TABLE_ADDR,
> @@ -72,6 +80,9 @@ static efi_system_table_t efi_systab __initdata;
> static u64 efi_var_store_size;
> static u64 efi_var_remaining_size;
> static u64 efi_var_max_var_size;
> +static u64 boot_used_size;
> +static u64 boot_var_size;
> +static u64 active_size;
>
> unsigned long x86_efi_facility;
>
> @@ -166,8 +177,53 @@ static efi_status_t virt_efi_get_next_variable(unsigned long *name_size,
> efi_char16_t *name,
> efi_guid_t *vendor)
> {
> - return efi_call_virt3(get_next_variable,
> - name_size, name, vendor);
> + efi_status_t status;
> + static bool finished = false;
> + static u64 var_size;
> +
> + status = efi_call_virt3(get_next_variable,
> + name_size, name, vendor);
> +
> + if (status == EFI_NOT_FOUND) {
> + finished = true;
> + if (var_size < boot_used_size) {
> + boot_var_size = boot_used_size - var_size;
> + active_size += boot_var_size;

Doesn't work sometimes. Here, if garbage is not collected, then
boot_used_size will contain the size of garbage, and get added into
active_size. This defeats the purpose of active_size.

I added a big printk before "return EFI_OUT_OF_RESOURCES", here are the
results (Dell XPS 8500, Secure Boot capable):

Failed to create variables in efivarfs, printk:
size=22
storage_size=131072
remaining_size=5230
max_size=5204
efi_var_store_size=131072
efi_var_remaining_size=5378
efi_var_max_var_size=5352
boot_used_size=125694
boot_var_size=125694
active_size=125694

After a few reboots,

EFI shell, QueryVariableInfo.efi:
MaximumVariableStorageSize=131072
RemainingVariableStorageSize=102113
MaximumVariableSize=65509

After several more pstore crash dumps, printk:
size=22
storage_size=131072
remaining_size=53064
max_size=53038
efi_var_store_size=131072
efi_var_remaining_size=53212
efi_var_max_var_size=53186
boot_used_size=77860
boot_var_size=77860
active_size=77860

After reboot, EFI shell, QueryVariableInfo.efi:
MaximumVariableStorageSize=131072
RemainingVariableStorageSize=50456
MaximumVariableSize=50430
-> reset ...
RemainingVariableStorageSize=47922
MaximumVariableSize=47896
-> reset ...
RemainingVariableStorageSize=45462
MaximumVariableSize=45436
-> reset ...
RemainingVariableStorageSize=43002
MaximumVariableSize=42976
-> reset ...
RemainingVariableStorageSize=40542
MaximumVariableSize=40516

Each reboot will consume some nvram? This is consistent with
http://article.gmane.org/gmane.linux.kernel.stable/47156

2013-04-22 15:03:31

by Paul Bolle

[permalink] [raw]
Subject: Re: [PATCH V6 2/3] efi: Pass boot services variable info to runtime code

On Mon, 2013-04-15 at 13:09 -0700, Matthew Garrett wrote:
> EFI variables can be flagged as being accessible only within boot services.
> This makes it awkward for us to figure out how much space they use at
> runtime. In theory we could figure this out by simply comparing the results
> from QueryVariableInfo() to the space used by all of our variables, but
> that fails if the platform doesn't garbage collect on every boot. Thankfully,
> calling QueryVariableInfo() while still inside boot services gives a more
> reliable answer. This patch passes that information from the EFI boot stub
> up to the efi platform code.
>
> Signed-off-by: Matthew Garrett <[email protected]>

This seems to be the patch that has become commit
cc5a080c5d40c36089bb08a8a16fa3fc7047fe0f ("efi: Pass boot services
variable info to runtime code"), which is part of v3.9-rc8.

It triggers this GCC warning (on Fedora 17, 32 bit):
arch/x86/boot/compressed/eboot.c: In function ‘setup_efi_vars’:
arch/x86/boot/compressed/eboot.c:269:2: warning: passing argument 1 of ‘efi_call_phys’ makes pointer from integer without a cast [enabled by default]
In file included from arch/x86/boot/compressed/eboot.c:12:0:
[...]/arch/x86/include/asm/efi.h:8:33: note: expected ‘void *’ but argument is of type ‘long unsigned int’

> --- a/arch/x86/boot/compressed/eboot.c
> +++ b/arch/x86/boot/compressed/eboot.c
> @@ -251,6 +251,51 @@ static void find_bits(unsigned long mask, u8 *pos, u8 *size)
> *size = len;
> }
>
> +static efi_status_t setup_efi_vars(struct boot_params *params)
> +{
> + struct setup_data *data;
> + struct efi_var_bootdata *efidata;
> + u64 store_size, remaining_size, var_size;
> + efi_status_t status;
> +
> + if (!sys_table->runtime->query_variable_info)
> + return EFI_UNSUPPORTED;
> +
> + data = (struct setup_data *)(unsigned long)params->hdr.setup_data;
> +
> + while (data && data->next)
> + data = (struct setup_data *)(unsigned long)data->next;
> +
> + status = efi_call_phys4(sys_table->runtime->query_variable_info,

Casting to void * here makes the warning go away. But the code is
sufficiently complicated (for me) to make it unobvious whether that is
the right thing to do. Besides, I don't think I have hardware to test
this.

> + EFI_VARIABLE_NON_VOLATILE |
> + EFI_VARIABLE_BOOTSERVICE_ACCESS |
> + EFI_VARIABLE_RUNTIME_ACCESS, &store_size,
> + &remaining_size, &var_size);
> +
> + if (status != EFI_SUCCESS)
> + return status;
> +
> + status = efi_call_phys3(sys_table->boottime->allocate_pool,
> + EFI_LOADER_DATA, sizeof(*efidata), &efidata);
> +
> + if (status != EFI_SUCCESS)
> + return status;
> +
> + efidata->data.type = SETUP_EFI_VARS;
> + efidata->data.len = sizeof(struct efi_var_bootdata) -
> + sizeof(struct setup_data);
> + efidata->data.next = 0;
> + efidata->store_size = store_size;
> + efidata->remaining_size = remaining_size;
> + efidata->max_var_size = var_size;
> +
> + if (data)
> + data->next = (unsigned long)efidata;
> + else
> + params->hdr.setup_data = (unsigned long)efidata;
> +
> +}
> +
> static efi_status_t setup_efi_pci(struct boot_params *params)
> {
> efi_pci_io_protocol *pci;


Paul Bolle

2013-04-24 10:09:50

by joeyli

[permalink] [raw]
Subject: Re: [PATCH V6 3/3] efi: Distinguish between "remaining space" and actually used space

Hi all,

於 一,2013-04-15 於 13:09 -0700,Matthew Garrett 提到:
> EFI implementations distinguish between space that is actively used by a
> variable and space that merely hasn't been garbage collected yet. Space
> that hasn't yet been garbage collected isn't available for use and so isn't
> counted in the remaining_space field returned by QueryVariableInfo().
>
> Combined with commit 68d9298 this can cause problems. Some implementations
> don't garbage collect until the remaining space is smaller than the maximum
> variable size, and as a result check_var_size() will always fail once more
> than 50% of the variable store has been used even if most of that space is
> marked as available for garbage collection. The user is unable to create
> new variables, and deleting variables doesn't increase the remaining space.
>
> The problem that 68d9298 was attempting to avoid was one where certain
> platforms fail if the actively used space is greater than 50% of the
> available storage space. We should be able to calculate that by simply
> summing the size of each available variable and subtracting that from
> the total storage space. With luck this will fix the problem described in
> https://bugzilla.kernel.org/show_bug.cgi?id=55471 without permitting
> damage to occur to the machines 68d9298 was attempting to fix.
>
> Signed-off-by: Matthew Garrett <[email protected]>
> ---
> arch/x86/platform/efi/efi.c | 109 +++++++++++++++++++++++++++++++++++++++++---
> 1 file changed, 102 insertions(+), 7 deletions(-)
>
> diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c
> index e844d82..a3f03cd 100644
> --- a/arch/x86/platform/efi/efi.c
> +++ b/arch/x86/platform/efi/efi.c
...
> @@ -1039,8 +1122,20 @@ efi_status_t efi_query_variable_store(u32 attributes, unsigned long size)
> if (status != EFI_SUCCESS)
> return status;
>
> - if (!storage_size || size > remaining_size || size > max_size ||
> - (remaining_size - size) < (storage_size / 2))
> + /*
> + * Some firmware implementations refuse to boot if there's insufficient
> + * space in the variable store. We account for that by refusing the
> + * write if permitting it would reduce the available space to under
> + * 50%. However, some firmware won't reclaim variable space until
> + * after the used (not merely the actively used) space drops below
> + * a threshold. We can approximate that case with the value calculated
> + * above. If both the firmware and our calculations indicate that the
> + * available space would drop below 50%, refuse the write.
> + */
> +
> + if (!storage_size || size > remaining_size ||
> + ((active_size + size + VAR_METADATA_SIZE > storage_size / 2) &&
> + (remaining_size - size < storage_size / 2)))

I am afraid it could not completely avoid to brick Samsung machines when
binding active_size and remaining_size logic with AND.

I don't have machine for testing, but my concern is we can run
delete/create variables many cycles at runtime for trigger brick Samsung
machines.
It causes the garbage size increased and remaining_size decreased. But
we still can create new variable because active_size doesn't increase
due to we delete variable before create new. So, the condition
"remaining_size - size < storage_size / 2" will not really hit because
active_size condition is pass.

And, here also can not use OR for binding active_size and remaining_size
logic, that causes many machines will not trigger garbage collection
because remaining_size will never tight.

Please let me know if I lost any other conditions or background
information.


Thanks a lot!
Joey Lee

2013-04-24 10:15:12

by Matthew Garrett

[permalink] [raw]
Subject: Re: [PATCH V6 3/3] efi: Distinguish between "remaining space" and actually used space

On Wed, 2013-04-24 at 18:08 +0800, joeyli wrote:

> It causes the garbage size increased and remaining_size decreased. But
> we still can create new variable because active_size doesn't increase
> due to we delete variable before create new. So, the condition
> "remaining_size - size < storage_size / 2" will not really hit because
> active_size condition is pass.

That's fine - the (limited) information we have from Samsung is that
there's no problem if all the space is dirty, only if all the space is
marked as active.

--
Matthew Garrett | [email protected]
????{.n?+???????+%?????ݶ??w??{.n?+????{??G?????{ay?ʇڙ?,j??f???h?????????z_??(?階?ݢj"???m??????G????????????&???~???iO???z??v?^?m???? ????????I?

2013-04-24 11:00:53

by joeyli

[permalink] [raw]
Subject: Re: [PATCH V6 3/3] efi: Distinguish between "remaining space" and actually used space

於 三,2013-04-24 於 10:14 +0000,Matthew Garrett 提到:
> On Wed, 2013-04-24 at 18:08 +0800, joeyli wrote:
>
> > It causes the garbage size increased and remaining_size decreased. But
> > we still can create new variable because active_size doesn't increase
> > due to we delete variable before create new. So, the condition
> > "remaining_size - size < storage_size / 2" will not really hit because
> > active_size condition is pass.
>
> That's fine - the (limited) information we have from Samsung is that
> there's no problem if all the space is dirty, only if all the space is
> marked as active.
>
> --
> Matthew Garrett | [email protected]

Got it! Thanks for your explanation!

Then why we don't just remove the "remaining_size" condition but only
monitor the active_size should not larger then 1/2 storage_size?

Does that because we allow one and only one VAR_METADATA_SIZE beyond the
1/2 storage? If so, why we don't just give ( 1/2 storage_size +
VAR_METADATA_SIZ) space for active_size using?


Thanks a lot!
Joey Lee

2013-04-24 11:57:28

by Matthew Garrett

[permalink] [raw]
Subject: Re: [PATCH V6 3/3] efi: Distinguish between "remaining space" and actually used space

On Wed, 2013-04-24 at 18:59 +0800, joeyli wrote:

> Then why we don't just remove the "remaining_size" condition but only
> monitor the active_size should not larger then 1/2 storage_size?

If we calculate active_size as using more than 50% of the storage space
but remaining_size says we still have more than 50% available, we should
trust remaining_size instead of active_size.

--
Matthew Garrett | [email protected]
????{.n?+???????+%?????ݶ??w??{.n?+????{??G?????{ay?ʇڙ?,j??f???h?????????z_??(?階?ݢj"???m??????G????????????&???~???iO???z??v?^?m???? ????????I?

2013-04-24 13:25:29

by joeyli

[permalink] [raw]
Subject: Re: [PATCH V6 3/3] efi: Distinguish between "remaining space" and actually used space

於 三,2013-04-24 於 11:57 +0000,Matthew Garrett 提到:
> On Wed, 2013-04-24 at 18:59 +0800, joeyli wrote:
>
> > Then why we don't just remove the "remaining_size" condition but only
> > monitor the active_size should not larger then 1/2 storage_size?
>
> If we calculate active_size as using more than 50% of the storage space
> but remaining_size says we still have more than 50% available, we should
> trust remaining_size instead of active_size.
>
> --
> Matthew Garrett | [email protected]

That makes sense because BIOS base on remaining_size to trigger garbage
collection.

Thanks for your clarification!

Joey Lee