* Update UEFI variable support
@ 2011-12-14 21:06 Matthew Garrett
2011-12-14 21:06 ` [PATCH 1/4] uefi: Populate runtime_version Matthew Garrett
` (3 more replies)
0 siblings, 4 replies; 20+ messages in thread
From: Matthew Garrett @ 2011-12-14 21:06 UTC (permalink / raw)
To: linux-kernel; +Cc: mikew, x86, hpa
Our EFI variable support is artifically limited at the moment. This patchset
adds support for arbitrary variable sizes, limited only by what the platform
supports. It also fixes a case where we were unnecessarily fiddling with
sysfs while in the process of shutting down or crashing, which triggered
some other issues.
^ permalink raw reply [flat|nested] 20+ messages in thread* [PATCH 1/4] uefi: Populate runtime_version 2011-12-14 21:06 Update UEFI variable support Matthew Garrett @ 2011-12-14 21:06 ` Matthew Garrett 2011-12-14 21:06 ` [PATCH 2/4] efi: Only create sysfs entries while booting or running Matthew Garrett ` (2 subsequent siblings) 3 siblings, 0 replies; 20+ messages in thread From: Matthew Garrett @ 2011-12-14 21:06 UTC (permalink / raw) To: linux-kernel; +Cc: mikew, x86, hpa, Matthew Garrett The runtime_version field isn't getting populated, causing various UEFI calls to fail. Fix. Signed-off-by: Matthew Garrett <mjg@redhat.com> --- arch/x86/platform/efi/efi.c | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-) diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c index 37718f0..4294cb5 100644 --- a/arch/x86/platform/efi/efi.c +++ b/arch/x86/platform/efi/efi.c @@ -552,6 +552,8 @@ void __init efi_init(void) * virtual mode. */ efi.get_time = phys_efi_get_time; + + efi.runtime_version = runtime->hdr.revision; } else printk(KERN_ERR "Could not map the EFI runtime service " "table!\n"); -- 1.7.7.1 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 2/4] efi: Only create sysfs entries while booting or running 2011-12-14 21:06 Update UEFI variable support Matthew Garrett 2011-12-14 21:06 ` [PATCH 1/4] uefi: Populate runtime_version Matthew Garrett @ 2011-12-14 21:06 ` Matthew Garrett 2011-12-14 21:32 ` Mike Waychison 2011-12-14 21:06 ` [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes Matthew Garrett 2011-12-14 21:06 ` [PATCH 4/4] EFI: Add support for QueryVariableInfo() call Matthew Garrett 3 siblings, 1 reply; 20+ messages in thread From: Matthew Garrett @ 2011-12-14 21:06 UTC (permalink / raw) To: linux-kernel; +Cc: mikew, x86, hpa, Matthew Garrett There's no point in creating sysfs entries while the machine's on its way down. Skip it in that case in order to avoid complexity. Signed-off-by: Matthew Garrett <mjg@redhat.com> --- drivers/firmware/efivars.c | 17 ++++++++++++----- 1 files changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index b0a8117..eb07f13 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -552,11 +552,18 @@ static int efi_pstore_write(enum pstore_type_id type, u64 *id, if (found) efivar_unregister(found); - if (size) - ret = efivar_create_sysfs_entry(efivars, - utf16_strsize(efi_name, - DUMP_NAME_LEN * 2), - efi_name, &vendor); + if (size) { + switch (system_state) { + case SYSTEM_BOOTING: + case SYSTEM_RUNNING: + ret = efivar_create_sysfs_entry(efivars, + utf16_strsize(efi_name, + DUMP_NAME_LEN * 2), + efi_name, &vendor); + default: + break; + } + } *id = part; return ret; -- 1.7.7.1 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 2/4] efi: Only create sysfs entries while booting or running 2011-12-14 21:06 ` [PATCH 2/4] efi: Only create sysfs entries while booting or running Matthew Garrett @ 2011-12-14 21:32 ` Mike Waychison 0 siblings, 0 replies; 20+ messages in thread From: Mike Waychison @ 2011-12-14 21:32 UTC (permalink / raw) To: Matthew Garrett; +Cc: linux-kernel, x86, hpa On Wed, Dec 14, 2011 at 1:06 PM, Matthew Garrett <mjg@redhat.com> wrote: > There's no point in creating sysfs entries while the machine's on its way > down. Skip it in that case in order to avoid complexity. > > Signed-off-by: Matthew Garrett <mjg@redhat.com> Acked-by: Mike Waychison <mikew@google.com> > --- > drivers/firmware/efivars.c | 17 ++++++++++++----- > 1 files changed, 12 insertions(+), 5 deletions(-) > > diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c > index b0a8117..eb07f13 100644 > --- a/drivers/firmware/efivars.c > +++ b/drivers/firmware/efivars.c > @@ -552,11 +552,18 @@ static int efi_pstore_write(enum pstore_type_id type, u64 *id, > if (found) > efivar_unregister(found); > > - if (size) > - ret = efivar_create_sysfs_entry(efivars, > - utf16_strsize(efi_name, > - DUMP_NAME_LEN * 2), > - efi_name, &vendor); > + if (size) { > + switch (system_state) { > + case SYSTEM_BOOTING: > + case SYSTEM_RUNNING: > + ret = efivar_create_sysfs_entry(efivars, > + utf16_strsize(efi_name, > + DUMP_NAME_LEN * 2), > + efi_name, &vendor); > + default: > + break; > + } > + } > > *id = part; > return ret; > -- > 1.7.7.1 > ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes 2011-12-14 21:06 Update UEFI variable support Matthew Garrett 2011-12-14 21:06 ` [PATCH 1/4] uefi: Populate runtime_version Matthew Garrett 2011-12-14 21:06 ` [PATCH 2/4] efi: Only create sysfs entries while booting or running Matthew Garrett @ 2011-12-14 21:06 ` Matthew Garrett 2011-12-14 22:14 ` Mike Waychison 2011-12-14 21:06 ` [PATCH 4/4] EFI: Add support for QueryVariableInfo() call Matthew Garrett 3 siblings, 1 reply; 20+ messages in thread From: Matthew Garrett @ 2011-12-14 21:06 UTC (permalink / raw) To: linux-kernel; +Cc: mikew, x86, hpa, Matthew Garrett The EFI variables code has a hard limit of 1024 bytes for variable length. This restriction only existed in version 0.99 of the EFI specification, and is not relevant on any existing hardware. Add support for a longer structure that incorporates the existing one, allowing old userspace to carry on working while allowing newer userspace to write larger variables via the same interface. Signed-off-by: Matthew Garrett <mjg@redhat.com> --- drivers/firmware/efivars.c | 240 ++++++++++++++++++++++++++++---------------- 1 files changed, 154 insertions(+), 86 deletions(-) diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index eb07f13..1bef80c 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -92,13 +92,6 @@ MODULE_VERSION(EFIVARS_VERSION); #define DUMP_NAME_LEN 52 -/* - * The maximum size of VariableName + Data = 1024 - * Therefore, it's reasonable to save that much - * space in each part of the structure, - * and we use a page for reading/writing. - */ - struct efi_variable { efi_char16_t VariableName[1024/sizeof(efi_char16_t)]; efi_guid_t VendorGuid; @@ -108,10 +101,20 @@ struct efi_variable { __u32 Attributes; } __attribute__((packed)); +/* + * Older versions of this driver had a fixed 1024-byte buffer. We need to + * maintain compatibility with old userspace, so we provide an extended + * structure to allow userspace to write larger variables + */ + +struct extended_efi_variable { + struct efi_variable header; + __u8 Data[0]; +} __attribute__((packed)); struct efivar_entry { struct efivars *efivars; - struct efi_variable var; + struct extended_efi_variable *var; struct list_head list; struct kobject kobj; }; @@ -192,21 +195,41 @@ utf16_strncmp(const efi_char16_t *a, const efi_char16_t *b, size_t len) } static efi_status_t -get_var_data_locked(struct efivars *efivars, struct efi_variable *var) +get_var_data_locked(struct efivars *efivars, struct extended_efi_variable **var) { efi_status_t status; + unsigned long length; + + if (!*var) + *var = kmalloc(sizeof(struct extended_efi_variable), GFP_KERNEL); + + (*var)->header.DataSize = 0; + status = efivars->ops->get_variable((*var)->header.VariableName, + &(*var)->header.VendorGuid, + &(*var)->header.Attributes, + &(*var)->header.DataSize, + (*var)->Data); + + if (status == EFI_BUFFER_TOO_SMALL) { + *var = krealloc(*var, sizeof(struct extended_efi_variable) + + (*var)->header.DataSize, GFP_KERNEL); + status = efivars->ops->get_variable((*var)->header.VariableName, + &(*var)->header.VendorGuid, + &(*var)->header.Attributes, + &(*var)->header.DataSize, + (*var)->Data); + } + + length = ((*var)->header.DataSize < 1024) ? (*var)->header.DataSize : + 1024; + + memcpy(&(*var)->header.Data, &(*var)->Data, length); - var->DataSize = 1024; - status = efivars->ops->get_variable(var->VariableName, - &var->VendorGuid, - &var->Attributes, - &var->DataSize, - var->Data); return status; } static efi_status_t -get_var_data(struct efivars *efivars, struct efi_variable *var) +get_var_data(struct efivars *efivars, struct extended_efi_variable **var) { efi_status_t status; @@ -224,13 +247,13 @@ get_var_data(struct efivars *efivars, struct efi_variable *var) static ssize_t efivar_guid_read(struct efivar_entry *entry, char *buf) { - struct efi_variable *var = &entry->var; + struct extended_efi_variable *var = entry->var; char *str = buf; if (!entry || !buf) return 0; - efi_guid_unparse(&var->VendorGuid, str); + efi_guid_unparse(&var->header.VendorGuid, str); str += strlen(str); str += sprintf(str, "\n"); @@ -240,22 +263,22 @@ efivar_guid_read(struct efivar_entry *entry, char *buf) static ssize_t efivar_attr_read(struct efivar_entry *entry, char *buf) { - struct efi_variable *var = &entry->var; + struct extended_efi_variable *var = entry->var; char *str = buf; efi_status_t status; if (!entry || !buf) return -EINVAL; - status = get_var_data(entry->efivars, var); + status = get_var_data(entry->efivars, &var); if (status != EFI_SUCCESS) return -EIO; - if (var->Attributes & 0x1) + if (var->header.Attributes & 0x1) str += sprintf(str, "EFI_VARIABLE_NON_VOLATILE\n"); - if (var->Attributes & 0x2) + if (var->header.Attributes & 0x2) str += sprintf(str, "EFI_VARIABLE_BOOTSERVICE_ACCESS\n"); - if (var->Attributes & 0x4) + if (var->header.Attributes & 0x4) str += sprintf(str, "EFI_VARIABLE_RUNTIME_ACCESS\n"); return str - buf; } @@ -263,36 +286,36 @@ efivar_attr_read(struct efivar_entry *entry, char *buf) static ssize_t efivar_size_read(struct efivar_entry *entry, char *buf) { - struct efi_variable *var = &entry->var; + struct extended_efi_variable *var = entry->var; char *str = buf; efi_status_t status; if (!entry || !buf) return -EINVAL; - status = get_var_data(entry->efivars, var); + status = get_var_data(entry->efivars, &var); if (status != EFI_SUCCESS) return -EIO; - str += sprintf(str, "0x%lx\n", var->DataSize); + str += sprintf(str, "0x%lx\n", var->header.DataSize); return str - buf; } static ssize_t efivar_data_read(struct efivar_entry *entry, char *buf) { - struct efi_variable *var = &entry->var; + struct extended_efi_variable *var = entry->var; efi_status_t status; if (!entry || !buf) return -EINVAL; - status = get_var_data(entry->efivars, var); + status = get_var_data(entry->efivars, &var); if (status != EFI_SUCCESS) return -EIO; - memcpy(buf, var->Data, var->DataSize); - return var->DataSize; + memcpy(buf, var->Data, var->header.DataSize); + return var->header.DataSize; } /* * We allow each variable to be edited via rewriting the @@ -301,34 +324,46 @@ efivar_data_read(struct efivar_entry *entry, char *buf) static ssize_t efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count) { - struct efi_variable *new_var, *var = &entry->var; + struct extended_efi_variable *new_var, *var = entry->var; + struct efi_variable *tmp_var = NULL; struct efivars *efivars = entry->efivars; efi_status_t status = EFI_NOT_FOUND; + int error = 0; - if (count != sizeof(struct efi_variable)) + if (count == sizeof(struct efi_variable)) { + tmp_var = (struct efi_variable *)buf; + new_var = kmalloc(sizeof(struct efi_variable) + + tmp_var->DataSize, GFP_KERNEL); + memcpy(&new_var->header, tmp_var, sizeof(struct efi_variable)); + memcpy(&new_var->Data, tmp_var->Data, tmp_var->DataSize); + } else if (count > sizeof(struct efi_variable)) { + new_var = (struct extended_efi_variable *)buf; + } else { return -EINVAL; + } - new_var = (struct efi_variable *)buf; /* * If only updating the variable data, then the name * and guid should remain the same */ - if (memcmp(new_var->VariableName, var->VariableName, sizeof(var->VariableName)) || - efi_guidcmp(new_var->VendorGuid, var->VendorGuid)) { + if (memcmp(new_var->header.VariableName, var->header.VariableName, sizeof(var->header.VariableName)) || + efi_guidcmp(new_var->header.VendorGuid, var->header.VendorGuid)) { printk(KERN_ERR "efivars: Cannot edit the wrong variable!\n"); - return -EINVAL; + error = -EINVAL; + goto out; } - if ((new_var->DataSize <= 0) || (new_var->Attributes == 0)){ + if ((new_var->header.DataSize <= 0) || (new_var->header.Attributes == 0)) { printk(KERN_ERR "efivars: DataSize & Attributes must be valid!\n"); - return -EINVAL; + error = -EINVAL; + goto out; } spin_lock(&efivars->lock); - status = efivars->ops->set_variable(new_var->VariableName, - &new_var->VendorGuid, - new_var->Attributes, - new_var->DataSize, + status = efivars->ops->set_variable(new_var->header.VariableName, + &new_var->header.VendorGuid, + new_var->header.Attributes, + new_var->header.DataSize, new_var->Data); spin_unlock(&efivars->lock); @@ -336,28 +371,37 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count) if (status != EFI_SUCCESS) { printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n", status); - return -EIO; + error = -EIO; + goto out; } - memcpy(&entry->var, new_var, count); + memcpy(entry->var, new_var, count); +out: + /* Free the extended structure if we needed to allocate it */ + if (tmp_var && new_var) + kfree(new_var); + + if (error) + return error; + return count; } static ssize_t efivar_show_raw(struct efivar_entry *entry, char *buf) { - struct efi_variable *var = &entry->var; + struct extended_efi_variable *var = entry->var; efi_status_t status; if (!entry || !buf) return 0; - status = get_var_data(entry->efivars, var); + status = get_var_data(entry->efivars, &var); if (status != EFI_SUCCESS) return -EIO; - memcpy(buf, var, sizeof(*var)); - return sizeof(*var); + memcpy(buf, var, sizeof(*var) + var->header.DataSize); + return sizeof(*var) + var->header.DataSize; } /* @@ -404,6 +448,7 @@ static const struct sysfs_ops efivar_attr_ops = { static void efivar_release(struct kobject *kobj) { struct efivar_entry *var = container_of(kobj, struct efivar_entry, kobj); + kfree(var->var); kfree(var); } @@ -468,21 +513,21 @@ static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type, unsigned long time; while (&efivars->walk_entry->list != &efivars->list) { - if (!efi_guidcmp(efivars->walk_entry->var.VendorGuid, + if (!efi_guidcmp(efivars->walk_entry->var->header.VendorGuid, vendor)) { for (i = 0; i < DUMP_NAME_LEN; i++) { - name[i] = efivars->walk_entry->var.VariableName[i]; + name[i] = efivars->walk_entry->var->header.VariableName[i]; } if (sscanf(name, "dump-type%u-%u-%lu", type, &part, &time) == 3) { *id = part; timespec->tv_sec = time; timespec->tv_nsec = 0; get_var_data_locked(efivars, &efivars->walk_entry->var); - size = efivars->walk_entry->var.DataSize; + size = efivars->walk_entry->var->header.DataSize; *buf = kmalloc(size, GFP_KERNEL); if (*buf == NULL) return -ENOMEM; - memcpy(*buf, efivars->walk_entry->var.Data, + memcpy(*buf, efivars->walk_entry->var->Data, size); efivars->walk_entry = list_entry(efivars->walk_entry->list.next, struct efivar_entry, list); @@ -521,19 +566,19 @@ static int efi_pstore_write(enum pstore_type_id type, u64 *id, list_for_each_entry(entry, &efivars->list, list) { get_var_data_locked(efivars, &entry->var); - if (efi_guidcmp(entry->var.VendorGuid, vendor)) + if (efi_guidcmp(entry->var->header.VendorGuid, vendor)) continue; - if (utf16_strncmp(entry->var.VariableName, efi_name, + if (utf16_strncmp(entry->var->header.VariableName, efi_name, utf16_strlen(efi_name))) continue; /* Needs to be a prefix */ - if (entry->var.VariableName[utf16_strlen(efi_name)] == 0) + if (entry->var->header.VariableName[utf16_strlen(efi_name)] == 0) continue; /* found */ found = entry; - efivars->ops->set_variable(entry->var.VariableName, - &entry->var.VendorGuid, + efivars->ops->set_variable(entry->var->header.VariableName, + &entry->var->header.VendorGuid, PSTORE_EFI_ATTRIBUTES, 0, NULL); } @@ -552,7 +597,7 @@ static int efi_pstore_write(enum pstore_type_id type, u64 *id, if (found) efivar_unregister(found); - if (size) { + if (size) switch (system_state) { case SYSTEM_BOOTING: case SYSTEM_RUNNING: @@ -563,7 +608,6 @@ static int efi_pstore_write(enum pstore_type_id type, u64 *id, default: break; } - } *id = part; return ret; @@ -621,62 +665,89 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj, struct bin_attribute *bin_attr, char *buf, loff_t pos, size_t count) { - struct efi_variable *new_var = (struct efi_variable *)buf; + struct efi_variable *new_var = NULL; + struct extended_efi_variable *ext_new_var = NULL; struct efivars *efivars = bin_attr->private; struct efivar_entry *search_efivar, *n; unsigned long strsize1, strsize2; efi_status_t status = EFI_NOT_FOUND; int found = 0; + int error = 0; if (!capable(CAP_SYS_ADMIN)) return -EACCES; + if (count > sizeof(struct efi_variable)) + ext_new_var = (struct extended_efi_variable *)buf; + else if (count == sizeof(struct efi_variable)) + new_var = (struct efi_variable *)buf; + else + return -EINVAL; + spin_lock(&efivars->lock); + if (new_var) { + ext_new_var = kmalloc(sizeof(struct extended_efi_variable) + + new_var->DataSize, GFP_KERNEL); + memcpy(&ext_new_var->header, new_var, sizeof(struct efi_variable)); + memcpy(ext_new_var->Data, new_var->Data, new_var->DataSize); + } + /* * 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 = utf16_strsize(search_efivar->var->header.VariableName, 1024); + strsize2 = utf16_strsize(ext_new_var->header.VariableName, 1024); if (strsize1 == strsize2 && - !memcmp(&(search_efivar->var.VariableName), - new_var->VariableName, strsize1) && - !efi_guidcmp(search_efivar->var.VendorGuid, - new_var->VendorGuid)) { + !memcmp(&(search_efivar->var->header.VariableName), + ext_new_var->header.VariableName, strsize1) && + !efi_guidcmp(search_efivar->var->header.VendorGuid, + ext_new_var->header.VendorGuid)) { found = 1; break; } } if (found) { spin_unlock(&efivars->lock); - return -EINVAL; + error = -EINVAL; + goto out; } /* now *really* create the variable via EFI */ - status = efivars->ops->set_variable(new_var->VariableName, - &new_var->VendorGuid, - new_var->Attributes, - new_var->DataSize, - new_var->Data); + status = efivars->ops->set_variable(ext_new_var->header.VariableName, + &ext_new_var->header.VendorGuid, + ext_new_var->header.Attributes, + ext_new_var->header.DataSize, + ext_new_var->Data); if (status != EFI_SUCCESS) { printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n", status); spin_unlock(&efivars->lock); - return -EIO; + error = -EIO; + goto out; } spin_unlock(&efivars->lock); /* Create the entry in sysfs. Locking is not required here */ status = efivar_create_sysfs_entry(efivars, - utf16_strsize(new_var->VariableName, + utf16_strsize(ext_new_var->header.VariableName, 1024), - new_var->VariableName, - &new_var->VendorGuid); + ext_new_var->header.VariableName, + &ext_new_var->header.VendorGuid); if (status) { printk(KERN_WARNING "efivars: variable created, but sysfs entry wasn't.\n"); } + +out: + /* Free the temporary structure if we needed it */ + if (new_var && ext_new_var) + kfree(ext_new_var); + + if (error) + return error; + return count; } @@ -700,12 +771,12 @@ 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); + strsize1 = utf16_strsize(search_efivar->var->header.VariableName, 1024); strsize2 = utf16_strsize(del_var->VariableName, 1024); if (strsize1 == strsize2 && - !memcmp(&(search_efivar->var.VariableName), + !memcmp(&(search_efivar->var->header.VariableName), del_var->VariableName, strsize1) && - !efi_guidcmp(search_efivar->var.VendorGuid, + !efi_guidcmp(search_efivar->var->header.VendorGuid, del_var->VendorGuid)) { found = 1; break; @@ -813,9 +884,11 @@ efivar_create_sysfs_entry(struct efivars *efivars, } new_efivar->efivars = efivars; - memcpy(new_efivar->var.VariableName, variable_name, + new_efivar->var = kzalloc(sizeof(struct extended_efi_variable), + GFP_KERNEL); + memcpy(new_efivar->var->header.VariableName, variable_name, variable_name_size); - memcpy(&(new_efivar->var.VendorGuid), vendor_guid, sizeof(efi_guid_t)); + memcpy(&(new_efivar->var->header.VendorGuid), vendor_guid, sizeof(efi_guid_t)); /* Convert Unicode to normal chars (assume top bits are 0), ala UTF-8 */ @@ -954,11 +1027,6 @@ int register_efivars(struct efivars *efivars, goto out; } - /* - * Per EFI spec, the maximum storage allocated for both - * the variable name and variable data is 1024 bytes. - */ - do { variable_name_size = 1024; -- 1.7.7.1 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes 2011-12-14 21:06 ` [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes Matthew Garrett @ 2011-12-14 22:14 ` Mike Waychison 2011-12-14 22:39 ` Matthew Garrett 0 siblings, 1 reply; 20+ messages in thread From: Mike Waychison @ 2011-12-14 22:14 UTC (permalink / raw) To: Matthew Garrett; +Cc: linux-kernel, x86, hpa On Wed, Dec 14, 2011 at 1:06 PM, Matthew Garrett <mjg@redhat.com> wrote: > The EFI variables code has a hard limit of 1024 bytes for variable length. > This restriction only existed in version 0.99 of the EFI specification, > and is not relevant on any existing hardware. Add support for a longer > structure that incorporates the existing one, allowing old userspace to > carry on working while allowing newer userspace to write larger variables > via the same interface. > > Signed-off-by: Matthew Garrett <mjg@redhat.com> > --- > drivers/firmware/efivars.c | 240 ++++++++++++++++++++++++++++---------------- > 1 files changed, 154 insertions(+), 86 deletions(-) > > diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c > index eb07f13..1bef80c 100644 > --- a/drivers/firmware/efivars.c > +++ b/drivers/firmware/efivars.c > @@ -92,13 +92,6 @@ MODULE_VERSION(EFIVARS_VERSION); > > #define DUMP_NAME_LEN 52 > > -/* > - * The maximum size of VariableName + Data = 1024 > - * Therefore, it's reasonable to save that much > - * space in each part of the structure, > - * and we use a page for reading/writing. > - */ > - > struct efi_variable { > efi_char16_t VariableName[1024/sizeof(efi_char16_t)]; > efi_guid_t VendorGuid; > @@ -108,10 +101,20 @@ struct efi_variable { > __u32 Attributes; > } __attribute__((packed)); > > +/* > + * Older versions of this driver had a fixed 1024-byte buffer. We need to > + * maintain compatibility with old userspace, so we provide an extended > + * structure to allow userspace to write larger variables > + */ > + > +struct extended_efi_variable { > + struct efi_variable header; > + __u8 Data[0]; > +} __attribute__((packed)); > > struct efivar_entry { > struct efivars *efivars; > - struct efi_variable var; > + struct extended_efi_variable *var; > struct list_head list; > struct kobject kobj; > }; > @@ -192,21 +195,41 @@ utf16_strncmp(const efi_char16_t *a, const efi_char16_t *b, size_t len) > } > > static efi_status_t > -get_var_data_locked(struct efivars *efivars, struct efi_variable *var) > +get_var_data_locked(struct efivars *efivars, struct extended_efi_variable **var) > { > efi_status_t status; > + unsigned long length; > + > + if (!*var) > + *var = kmalloc(sizeof(struct extended_efi_variable), GFP_KERNEL); Aren't we holding a spinlock here? > + > + (*var)->header.DataSize = 0; > + status = efivars->ops->get_variable((*var)->header.VariableName, > + &(*var)->header.VendorGuid, > + &(*var)->header.Attributes, > + &(*var)->header.DataSize, > + (*var)->Data); This doesn't look right. ->Data here is after the Data[1024] buffer embedded in (*var)->header, and a read into this buffer will corrupt the heap. > + > + if (status == EFI_BUFFER_TOO_SMALL) { > + *var = krealloc(*var, sizeof(struct extended_efi_variable) + > + (*var)->header.DataSize, GFP_KERNEL); > + status = efivars->ops->get_variable((*var)->header.VariableName, > + &(*var)->header.VendorGuid, > + &(*var)->header.Attributes, > + &(*var)->header.DataSize, > + (*var)->Data); > + } > + > + length = ((*var)->header.DataSize < 1024) ? (*var)->header.DataSize : > + 1024; > + > + memcpy(&(*var)->header.Data, &(*var)->Data, length); This memcpy clobbers the header.Data with the corrupted data when we didn't use the second path. > > - var->DataSize = 1024; > - status = efivars->ops->get_variable(var->VariableName, > - &var->VendorGuid, > - &var->Attributes, > - &var->DataSize, > - var->Data); > return status; > } > > static efi_status_t > -get_var_data(struct efivars *efivars, struct efi_variable *var) > +get_var_data(struct efivars *efivars, struct extended_efi_variable **var) > { > efi_status_t status; > > @@ -224,13 +247,13 @@ get_var_data(struct efivars *efivars, struct efi_variable *var) > static ssize_t > efivar_guid_read(struct efivar_entry *entry, char *buf) > { > - struct efi_variable *var = &entry->var; > + struct extended_efi_variable *var = entry->var; > char *str = buf; > > if (!entry || !buf) > return 0; > > - efi_guid_unparse(&var->VendorGuid, str); > + efi_guid_unparse(&var->header.VendorGuid, str); > str += strlen(str); > str += sprintf(str, "\n"); > > @@ -240,22 +263,22 @@ efivar_guid_read(struct efivar_entry *entry, char *buf) > static ssize_t > efivar_attr_read(struct efivar_entry *entry, char *buf) > { > - struct efi_variable *var = &entry->var; > + struct extended_efi_variable *var = entry->var; > char *str = buf; > efi_status_t status; > > if (!entry || !buf) > return -EINVAL; > > - status = get_var_data(entry->efivars, var); > + status = get_var_data(entry->efivars, &var); > if (status != EFI_SUCCESS) > return -EIO; > > - if (var->Attributes & 0x1) > + if (var->header.Attributes & 0x1) > str += sprintf(str, "EFI_VARIABLE_NON_VOLATILE\n"); > - if (var->Attributes & 0x2) > + if (var->header.Attributes & 0x2) > str += sprintf(str, "EFI_VARIABLE_BOOTSERVICE_ACCESS\n"); > - if (var->Attributes & 0x4) > + if (var->header.Attributes & 0x4) > str += sprintf(str, "EFI_VARIABLE_RUNTIME_ACCESS\n"); > return str - buf; > } > @@ -263,36 +286,36 @@ efivar_attr_read(struct efivar_entry *entry, char *buf) > static ssize_t > efivar_size_read(struct efivar_entry *entry, char *buf) > { > - struct efi_variable *var = &entry->var; > + struct extended_efi_variable *var = entry->var; > char *str = buf; > efi_status_t status; > > if (!entry || !buf) > return -EINVAL; > > - status = get_var_data(entry->efivars, var); > + status = get_var_data(entry->efivars, &var); > if (status != EFI_SUCCESS) > return -EIO; > > - str += sprintf(str, "0x%lx\n", var->DataSize); > + str += sprintf(str, "0x%lx\n", var->header.DataSize); > return str - buf; > } > > static ssize_t > efivar_data_read(struct efivar_entry *entry, char *buf) > { > - struct efi_variable *var = &entry->var; > + struct extended_efi_variable *var = entry->var; > efi_status_t status; > > if (!entry || !buf) > return -EINVAL; > > - status = get_var_data(entry->efivars, var); > + status = get_var_data(entry->efivars, &var); > if (status != EFI_SUCCESS) > return -EIO; > > - memcpy(buf, var->Data, var->DataSize); > - return var->DataSize; > + memcpy(buf, var->Data, var->header.DataSize); > + return var->header.DataSize; > } > /* > * We allow each variable to be edited via rewriting the > @@ -301,34 +324,46 @@ efivar_data_read(struct efivar_entry *entry, char *buf) > static ssize_t > efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count) > { > - struct efi_variable *new_var, *var = &entry->var; > + struct extended_efi_variable *new_var, *var = entry->var; > + struct efi_variable *tmp_var = NULL; > struct efivars *efivars = entry->efivars; > efi_status_t status = EFI_NOT_FOUND; > + int error = 0; > > - if (count != sizeof(struct efi_variable)) > + if (count == sizeof(struct efi_variable)) { > + tmp_var = (struct efi_variable *)buf; > + new_var = kmalloc(sizeof(struct efi_variable) + > + tmp_var->DataSize, GFP_KERNEL); > + memcpy(&new_var->header, tmp_var, sizeof(struct efi_variable)); > + memcpy(&new_var->Data, tmp_var->Data, tmp_var->DataSize); > + } else if (count > sizeof(struct efi_variable)) { > + new_var = (struct extended_efi_variable *)buf; > + } else { > return -EINVAL; > + } Ugh. This is difficult to follow, and complicates the memory freeing path :( We need to be careful here. The store_raw ABI is broken, in the sense that the ABI from compat mode differs from that in 32bit mode (there is a long in the efi_variable structure which changes the offsets). I don't know how to fix it properly and still maintain proper ABI compatibility. What are your thoughts on _not_ wrapping efi_variable with extended_efi_variable, and instead just using a "internal_efi_variable" structure that we copy stuff into/outof. I think that would make the memory management for dealing with the different sizes a lot easier to follow. ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes 2011-12-14 22:14 ` Mike Waychison @ 2011-12-14 22:39 ` Matthew Garrett 2011-12-14 22:44 ` H. Peter Anvin 2011-12-14 22:57 ` Mike Waychison 0 siblings, 2 replies; 20+ messages in thread From: Matthew Garrett @ 2011-12-14 22:39 UTC (permalink / raw) To: Mike Waychison; +Cc: linux-kernel, x86, hpa On Wed, Dec 14, 2011 at 02:14:27PM -0800, Mike Waychison wrote: > > static efi_status_t > > -get_var_data_locked(struct efivars *efivars, struct efi_variable *var) > > +get_var_data_locked(struct efivars *efivars, struct extended_efi_variable **var) > > { > > efi_status_t status; > > + unsigned long length; > > + > > + if (!*var) > > + *var = kmalloc(sizeof(struct extended_efi_variable), GFP_KERNEL); > > Aren't we holding a spinlock here? Good point. > > + > > + (*var)->header.DataSize = 0; > > + status = efivars->ops->get_variable((*var)->header.VariableName, > > + &(*var)->header.VendorGuid, > > + &(*var)->header.Attributes, > > + &(*var)->header.DataSize, > > + (*var)->Data); > > This doesn't look right. ->Data here is after the Data[1024] buffer > embedded in (*var)->header, and a read into this buffer will corrupt > the heap. DataSize is 0, so we'll never actually read anything back here. > > + > > + if (status == EFI_BUFFER_TOO_SMALL) { > > + *var = krealloc(*var, sizeof(struct extended_efi_variable) + > > + (*var)->header.DataSize, GFP_KERNEL); > > + status = efivars->ops->get_variable((*var)->header.VariableName, > > + &(*var)->header.VendorGuid, > > + &(*var)->header.Attributes, > > + &(*var)->header.DataSize, > > + (*var)->Data); > > + } > > + > > + length = ((*var)->header.DataSize < 1024) ? (*var)->header.DataSize : > > + 1024; > > + > > + memcpy(&(*var)->header.Data, &(*var)->Data, length); > > This memcpy clobbers the header.Data with the corrupted data when we > didn't use the second path. We'll always follow the second path providing there's actually data to read back. If there isn't then length will be 0. > > + if (count == sizeof(struct efi_variable)) { > > + tmp_var = (struct efi_variable *)buf; > > + new_var = kmalloc(sizeof(struct efi_variable) + > > + tmp_var->DataSize, GFP_KERNEL); > > + memcpy(&new_var->header, tmp_var, sizeof(struct efi_variable)); > > + memcpy(&new_var->Data, tmp_var->Data, tmp_var->DataSize); > > + } else if (count > sizeof(struct efi_variable)) { > > + new_var = (struct extended_efi_variable *)buf; > > + } else { > > return -EINVAL; > > + } > > Ugh. This is difficult to follow, and complicates the memory freeing path :( Entirely agreed. > We need to be careful here. The store_raw ABI is broken, in the sense > that the ABI from compat mode differs from that in 32bit mode (there > is a long in the efi_variable structure which changes the offsets). I > don't know how to fix it properly and still maintain proper ABI > compatibility. True. > What are your thoughts on _not_ wrapping efi_variable with > extended_efi_variable, and instead just using a > "internal_efi_variable" structure that we copy stuff into/outof. I > think that would make the memory management for dealing with the > different sizes a lot easier to follow. Hm. I think that'd only work if we expose a new interface. Writes would be easy enough to handle, but reads still need to work for old apps. -- Matthew Garrett | mjg59@srcf.ucam.org ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes 2011-12-14 22:39 ` Matthew Garrett @ 2011-12-14 22:44 ` H. Peter Anvin 2011-12-14 22:57 ` Matthew Garrett 2011-12-14 22:57 ` Mike Waychison 1 sibling, 1 reply; 20+ messages in thread From: H. Peter Anvin @ 2011-12-14 22:44 UTC (permalink / raw) To: Matthew Garrett; +Cc: Mike Waychison, linux-kernel, x86 On 12/14/2011 02:39 PM, Matthew Garrett wrote: > >> We need to be careful here. The store_raw ABI is broken, in the sense >> that the ABI from compat mode differs from that in 32bit mode (there >> is a long in the efi_variable structure which changes the offsets). I >> don't know how to fix it properly and still maintain proper ABI >> compatibility. > > True. > >> What are your thoughts on _not_ wrapping efi_variable with >> extended_efi_variable, and instead just using a >> "internal_efi_variable" structure that we copy stuff into/outof. I >> think that would make the memory management for dealing with the >> different sizes a lot easier to follow. > > Hm. I think that'd only work if we expose a new interface. Writes would > be easy enough to handle, but reads still need to work for old apps. > Would making the old ABI readonly and add a new write interface resolve the problem with compat mode? -hpa ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes 2011-12-14 22:44 ` H. Peter Anvin @ 2011-12-14 22:57 ` Matthew Garrett 2011-12-14 22:58 ` H. Peter Anvin 0 siblings, 1 reply; 20+ messages in thread From: Matthew Garrett @ 2011-12-14 22:57 UTC (permalink / raw) To: H. Peter Anvin; +Cc: Mike Waychison, linux-kernel, x86 On Wed, Dec 14, 2011 at 02:44:34PM -0800, H. Peter Anvin wrote: > On 12/14/2011 02:39 PM, Matthew Garrett wrote: > >> What are your thoughts on _not_ wrapping efi_variable with > >> extended_efi_variable, and instead just using a > >> "internal_efi_variable" structure that we copy stuff into/outof. I > >> think that would make the memory management for dealing with the > >> different sizes a lot easier to follow. > > > > Hm. I think that'd only work if we expose a new interface. Writes would > > be easy enough to handle, but reads still need to work for old apps. > > > > Would making the old ABI readonly and add a new write interface resolve > the problem with compat mode? Yes, but we'd break existing userspace. -- Matthew Garrett | mjg59@srcf.ucam.org ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes 2011-12-14 22:57 ` Matthew Garrett @ 2011-12-14 22:58 ` H. Peter Anvin 2011-12-15 15:44 ` Matthew Garrett 0 siblings, 1 reply; 20+ messages in thread From: H. Peter Anvin @ 2011-12-14 22:58 UTC (permalink / raw) To: Matthew Garrett; +Cc: Mike Waychison, linux-kernel, x86 On 12/14/2011 02:57 PM, Matthew Garrett wrote: > On Wed, Dec 14, 2011 at 02:44:34PM -0800, H. Peter Anvin wrote: >> On 12/14/2011 02:39 PM, Matthew Garrett wrote: >>>> What are your thoughts on _not_ wrapping efi_variable with >>>> extended_efi_variable, and instead just using a >>>> "internal_efi_variable" structure that we copy stuff into/outof. I >>>> think that would make the memory management for dealing with the >>>> different sizes a lot easier to follow. >>> >>> Hm. I think that'd only work if we expose a new interface. Writes would >>> be easy enough to handle, but reads still need to work for old apps. >>> >> >> Would making the old ABI readonly and add a new write interface resolve >> the problem with compat mode? > > Yes, but we'd break existing userspace. > Obviously. However, you indicated above that that might be acceptable -- how many things use this ABI to set variables? -hpa ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes 2011-12-14 22:58 ` H. Peter Anvin @ 2011-12-15 15:44 ` Matthew Garrett 0 siblings, 0 replies; 20+ messages in thread From: Matthew Garrett @ 2011-12-15 15:44 UTC (permalink / raw) To: H. Peter Anvin; +Cc: Mike Waychison, linux-kernel, x86 On Wed, Dec 14, 2011 at 02:58:20PM -0800, H. Peter Anvin wrote: > On 12/14/2011 02:57 PM, Matthew Garrett wrote: > > Yes, but we'd break existing userspace. > > > > Obviously. However, you indicated above that that might be acceptable > -- how many things use this ABI to set variables? Ah, sorry - my suggestion had been to add an additional interface, not just replace the existing one. efibootmgr is the only userland I know of that interacts directly, but it's entirely possible that there are others - I'd guess Google have something. -- Matthew Garrett | mjg59@srcf.ucam.org ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes 2011-12-14 22:39 ` Matthew Garrett 2011-12-14 22:44 ` H. Peter Anvin @ 2011-12-14 22:57 ` Mike Waychison 2011-12-14 23:00 ` H. Peter Anvin 2011-12-15 15:43 ` Matthew Garrett 1 sibling, 2 replies; 20+ messages in thread From: Mike Waychison @ 2011-12-14 22:57 UTC (permalink / raw) To: Matthew Garrett; +Cc: linux-kernel, x86, hpa On Wed, Dec 14, 2011 at 2:39 PM, Matthew Garrett <mjg59@srcf.ucam.org> wrote: > On Wed, Dec 14, 2011 at 02:14:27PM -0800, Mike Waychison wrote: > >> > static efi_status_t >> > -get_var_data_locked(struct efivars *efivars, struct efi_variable *var) >> > +get_var_data_locked(struct efivars *efivars, struct extended_efi_variable **var) >> > { >> > efi_status_t status; >> > + unsigned long length; >> > + >> > + if (!*var) >> > + *var = kmalloc(sizeof(struct extended_efi_variable), GFP_KERNEL); >> >> Aren't we holding a spinlock here? > > Good point. > >> > + >> > + (*var)->header.DataSize = 0; >> > + status = efivars->ops->get_variable((*var)->header.VariableName, >> > + &(*var)->header.VendorGuid, >> > + &(*var)->header.Attributes, >> > + &(*var)->header.DataSize, >> > + (*var)->Data); >> >> This doesn't look right. ->Data here is after the Data[1024] buffer >> embedded in (*var)->header, and a read into this buffer will corrupt >> the heap. > > DataSize is 0, so we'll never actually read anything back here. Ah. I missed that. Hmm, I wonder if this actually works :) > >> > + >> > + if (status == EFI_BUFFER_TOO_SMALL) { >> > + *var = krealloc(*var, sizeof(struct extended_efi_variable) + >> > + (*var)->header.DataSize, GFP_KERNEL); >> > + status = efivars->ops->get_variable((*var)->header.VariableName, >> > + &(*var)->header.VendorGuid, >> > + &(*var)->header.Attributes, >> > + &(*var)->header.DataSize, >> > + (*var)->Data); >> > + } >> > + >> > + length = ((*var)->header.DataSize < 1024) ? (*var)->header.DataSize : >> > + 1024; >> > + >> > + memcpy(&(*var)->header.Data, &(*var)->Data, length); >> >> This memcpy clobbers the header.Data with the corrupted data when we >> didn't use the second path. > > We'll always follow the second path providing there's actually data to > read back. If there isn't then length will be 0. > >> > + if (count == sizeof(struct efi_variable)) { >> > + tmp_var = (struct efi_variable *)buf; >> > + new_var = kmalloc(sizeof(struct efi_variable) + >> > + tmp_var->DataSize, GFP_KERNEL); >> > + memcpy(&new_var->header, tmp_var, sizeof(struct efi_variable)); >> > + memcpy(&new_var->Data, tmp_var->Data, tmp_var->DataSize); >> > + } else if (count > sizeof(struct efi_variable)) { >> > + new_var = (struct extended_efi_variable *)buf; >> > + } else { >> > return -EINVAL; >> > + } >> >> Ugh. This is difficult to follow, and complicates the memory freeing path :( > > Entirely agreed. > >> We need to be careful here. The store_raw ABI is broken, in the sense >> that the ABI from compat mode differs from that in 32bit mode (there >> is a long in the efi_variable structure which changes the offsets). I >> don't know how to fix it properly and still maintain proper ABI >> compatibility. > > True. > >> What are your thoughts on _not_ wrapping efi_variable with >> extended_efi_variable, and instead just using a >> "internal_efi_variable" structure that we copy stuff into/outof. I >> think that would make the memory management for dealing with the >> different sizes a lot easier to follow. > > Hm. I think that'd only work if we expose a new interface. Writes would > be easy enough to handle, but reads still need to work for old apps. Well,l we could *not* support returning all the data field for datasize > 1024, and simply truncate the field. We are limited by PAGE_SIZE by sysfs here anyway (so we don't really want to have a variable size memcpy in efivar_show_raw). ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes 2011-12-14 22:57 ` Mike Waychison @ 2011-12-14 23:00 ` H. Peter Anvin 2011-12-14 23:06 ` Mike Waychison 2011-12-15 15:43 ` Matthew Garrett 1 sibling, 1 reply; 20+ messages in thread From: H. Peter Anvin @ 2011-12-14 23:00 UTC (permalink / raw) To: Mike Waychison; +Cc: Matthew Garrett, linux-kernel, x86 On 12/14/2011 02:57 PM, Mike Waychison wrote: > > Well,l we could *not* support returning all the data field for > datasize > 1024, and simply truncate the field. We are limited by > PAGE_SIZE by sysfs here anyway (so we don't really want to have a > variable size memcpy in efivar_show_raw). > That may be the biggest reason to avoid sysfs. As far as I know sysfs doesn't allow seq_file to be used. -hpa ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes 2011-12-14 23:00 ` H. Peter Anvin @ 2011-12-14 23:06 ` Mike Waychison 2011-12-14 23:22 ` H. Peter Anvin 0 siblings, 1 reply; 20+ messages in thread From: Mike Waychison @ 2011-12-14 23:06 UTC (permalink / raw) To: H. Peter Anvin; +Cc: Matthew Garrett, linux-kernel, x86 On Wed, Dec 14, 2011 at 3:00 PM, H. Peter Anvin <hpa@zytor.com> wrote: > On 12/14/2011 02:57 PM, Mike Waychison wrote: >> >> Well,l we could *not* support returning all the data field for >> datasize > 1024, and simply truncate the field. We are limited by >> PAGE_SIZE by sysfs here anyway (so we don't really want to have a >> variable size memcpy in efivar_show_raw). >> > > That may be the biggest reason to avoid sysfs. As far as I know sysfs > doesn't allow seq_file to be used. > Completely agreed. I don't think a seq_file is warranted in this case in particular, but the dummification of the interfaces in sysfs sure makes it hard to do anything that isn't a "single value string". ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes 2011-12-14 23:06 ` Mike Waychison @ 2011-12-14 23:22 ` H. Peter Anvin 2011-12-14 23:24 ` Mike Waychison 0 siblings, 1 reply; 20+ messages in thread From: H. Peter Anvin @ 2011-12-14 23:22 UTC (permalink / raw) To: Mike Waychison; +Cc: Matthew Garrett, linux-kernel, x86 On 12/14/2011 03:06 PM, Mike Waychison wrote: > On Wed, Dec 14, 2011 at 3:00 PM, H. Peter Anvin <hpa@zytor.com> wrote: >> On 12/14/2011 02:57 PM, Mike Waychison wrote: >>> >>> Well,l we could *not* support returning all the data field for >>> datasize > 1024, and simply truncate the field. We are limited by >>> PAGE_SIZE by sysfs here anyway (so we don't really want to have a >>> variable size memcpy in efivar_show_raw). >>> >> >> That may be the biggest reason to avoid sysfs. As far as I know sysfs >> doesn't allow seq_file to be used. >> > > Completely agreed. I don't think a seq_file is warranted in this case > in particular, but the dummification of the interfaces in sysfs sure > makes it hard to do anything that isn't a "single value string". > Well, seq_file is a good way to deal with arbitrary length data. -hpa ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes 2011-12-14 23:22 ` H. Peter Anvin @ 2011-12-14 23:24 ` Mike Waychison 0 siblings, 0 replies; 20+ messages in thread From: Mike Waychison @ 2011-12-14 23:24 UTC (permalink / raw) To: H. Peter Anvin; +Cc: Matthew Garrett, linux-kernel, x86 On Wed, Dec 14, 2011 at 3:22 PM, H. Peter Anvin <hpa@zytor.com> wrote: > On 12/14/2011 03:06 PM, Mike Waychison wrote: >> On Wed, Dec 14, 2011 at 3:00 PM, H. Peter Anvin <hpa@zytor.com> wrote: >>> On 12/14/2011 02:57 PM, Mike Waychison wrote: >>>> >>>> Well,l we could *not* support returning all the data field for >>>> datasize > 1024, and simply truncate the field. We are limited by >>>> PAGE_SIZE by sysfs here anyway (so we don't really want to have a >>>> variable size memcpy in efivar_show_raw). >>>> >>> >>> That may be the biggest reason to avoid sysfs. As far as I know sysfs >>> doesn't allow seq_file to be used. >>> >> >> Completely agreed. I don't think a seq_file is warranted in this case >> in particular, but the dummification of the interfaces in sysfs sure >> makes it hard to do anything that isn't a "single value string". >> > > Well, seq_file is a good way to deal with arbitrary length data. > seq_file maps well to arbitrary record counts (keeping records self-consistent), but not so well for arbitrarily large records. ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes 2011-12-14 22:57 ` Mike Waychison 2011-12-14 23:00 ` H. Peter Anvin @ 2011-12-15 15:43 ` Matthew Garrett 2011-12-15 15:46 ` H. Peter Anvin 1 sibling, 1 reply; 20+ messages in thread From: Matthew Garrett @ 2011-12-15 15:43 UTC (permalink / raw) To: Mike Waychison; +Cc: linux-kernel, x86, hpa On Wed, Dec 14, 2011 at 02:57:51PM -0800, Mike Waychison wrote: > On Wed, Dec 14, 2011 at 2:39 PM, Matthew Garrett <mjg59@srcf.ucam.org> wrote: > > Hm. I think that'd only work if we expose a new interface. Writes would > > be easy enough to handle, but reads still need to work for old apps. > > Well,l we could *not* support returning all the data field for > datasize > 1024, and simply truncate the field. We are limited by > PAGE_SIZE by sysfs here anyway (so we don't really want to have a > variable size memcpy in efivar_show_raw). We'll pretty much definitely want to be able to read values > 1024 once people start shoving keys in there. The PAGE_SIZE limit is one that I'd forgotten about, though. -- Matthew Garrett | mjg59@srcf.ucam.org ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes 2011-12-15 15:43 ` Matthew Garrett @ 2011-12-15 15:46 ` H. Peter Anvin 2011-12-15 15:48 ` Matthew Garrett 0 siblings, 1 reply; 20+ messages in thread From: H. Peter Anvin @ 2011-12-15 15:46 UTC (permalink / raw) To: Matthew Garrett; +Cc: Mike Waychison, linux-kernel, x86 On 12/15/2011 07:43 AM, Matthew Garrett wrote: > > We'll pretty much definitely want to be able to read values> 1024 once > people start shoving keys in there. The PAGE_SIZE limit is one that I'd > forgotten about, though. > The jump from 1024 to 4096 seems to not be very long... hence I think we may need a different approach. ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes 2011-12-15 15:46 ` H. Peter Anvin @ 2011-12-15 15:48 ` Matthew Garrett 0 siblings, 0 replies; 20+ messages in thread From: Matthew Garrett @ 2011-12-15 15:48 UTC (permalink / raw) To: H. Peter Anvin; +Cc: Mike Waychison, linux-kernel, x86 On Thu, Dec 15, 2011 at 07:46:44AM -0800, H. Peter Anvin wrote: > On 12/15/2011 07:43 AM, Matthew Garrett wrote: > > > >We'll pretty much definitely want to be able to read values> 1024 once > >people start shoving keys in there. The PAGE_SIZE limit is one that I'd > >forgotten about, though. > > > > The jump from 1024 to 4096 seems to not be very long... hence I > think we may need a different approach. Yeah. I'll start thinking about the most sensible filesystem interface for this. -- Matthew Garrett | mjg59@srcf.ucam.org ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 4/4] EFI: Add support for QueryVariableInfo() call 2011-12-14 21:06 Update UEFI variable support Matthew Garrett ` (2 preceding siblings ...) 2011-12-14 21:06 ` [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes Matthew Garrett @ 2011-12-14 21:06 ` Matthew Garrett 3 siblings, 0 replies; 20+ messages in thread From: Matthew Garrett @ 2011-12-14 21:06 UTC (permalink / raw) To: linux-kernel; +Cc: mikew, x86, hpa, Matthew Garrett UEFI 2.0 and later provides a call for identifying hardware variable capabilities. Use this to work out the maximum size of the variables we can store, and throw errors where appropriate. Signed-off-by: Matthew Garrett <mjg@redhat.com> --- drivers/firmware/efivars.c | 53 +++++++++++++++++++++++++++++++++++++--- drivers/firmware/google/gsmi.c | 9 +++++++ include/linux/efi.h | 1 + 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index 1bef80c..805dba7 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c @@ -327,8 +327,9 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count) struct extended_efi_variable *new_var, *var = entry->var; struct efi_variable *tmp_var = NULL; struct efivars *efivars = entry->efivars; - efi_status_t status = EFI_NOT_FOUND; + efi_status_t status; int error = 0; + u64 storage_space, remaining_space, max_variable_size, size; if (count == sizeof(struct efi_variable)) { tmp_var = (struct efi_variable *)buf; @@ -360,6 +361,22 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count) } spin_lock(&efivars->lock); + + size = new_var->header.DataSize + + utf16_strnlen(new_var->header.VariableName, 512) * 2; + + status = efivars->ops->query_variable_info(new_var->header.Attributes, + &storage_space, + &remaining_space, + &max_variable_size); + if (status == EFI_SUCCESS) { + if (size > remaining_space || size > max_variable_size) { + spin_unlock(&efivars->lock); + error = -ENOSPC; + goto out; + } + } + status = efivars->ops->set_variable(new_var->header.VariableName, &new_var->header.VendorGuid, new_var->header.Attributes, @@ -670,9 +687,10 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj, struct efivars *efivars = bin_attr->private; struct efivar_entry *search_efivar, *n; unsigned long strsize1, strsize2; - efi_status_t status = EFI_NOT_FOUND; + efi_status_t status; int found = 0; int error = 0; + u64 storage_space, remaining_space, max_variable_size, size; if (!capable(CAP_SYS_ADMIN)) return -EACCES; @@ -693,6 +711,21 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj, memcpy(ext_new_var->Data, new_var->Data, new_var->DataSize); } + size = ext_new_var->header.DataSize + + utf16_strnlen(ext_new_var->header.VariableName, 512) * 2; + + status = efivars->ops->query_variable_info(ext_new_var->header.Attributes, + &storage_space, + &remaining_space, + &max_variable_size); + if (status == EFI_SUCCESS) { + if (size > remaining_space || size > max_variable_size) { + spin_unlock(&efivars->lock); + error = -ENOSPC; + goto out; + } + } + /* * Does this variable already exist? */ @@ -1009,6 +1042,7 @@ int register_efivars(struct efivars *efivars, efi_char16_t *variable_name; unsigned long variable_name_size = 1024; int error = 0; + u64 storage_space, remaining_space, max_variable_size; variable_name = kzalloc(variable_name_size, GFP_KERNEL); if (!variable_name) { @@ -1056,9 +1090,19 @@ int register_efivars(struct efivars *efivars, efivars->efi_pstore_info = efi_pstore_info; - efivars->efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL); + status = efivars->ops->query_variable_info(PSTORE_EFI_ATTRIBUTES, + &storage_space, + &remaining_space, + &max_variable_size); + if (status != EFI_SUCCESS) + max_variable_size = 1024; + + max_variable_size -= DUMP_NAME_LEN * 2; + + efivars->efi_pstore_info.buf = kmalloc(max_variable_size, GFP_KERNEL); + if (efivars->efi_pstore_info.buf) { - efivars->efi_pstore_info.bufsize = 1024; + efivars->efi_pstore_info.bufsize = max_variable_size; efivars->efi_pstore_info.data = efivars; spin_lock_init(&efivars->efi_pstore_info.buf_lock); pstore_register(&efivars->efi_pstore_info); @@ -1103,6 +1147,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_info = efi.query_variable_info; error = register_efivars(&__efivars, &ops, efi_kobj); if (error) goto err_put; diff --git a/drivers/firmware/google/gsmi.c b/drivers/firmware/google/gsmi.c index c4e7c59..8600e3f 100644 --- a/drivers/firmware/google/gsmi.c +++ b/drivers/firmware/google/gsmi.c @@ -419,6 +419,14 @@ static efi_status_t gsmi_get_next_variable(unsigned long *name_size, return ret; } +static efi_status_t gsmi_query_variable_info(u32 attr, + u64 *storage_space, + u64 *remaining_space, + u64 *max_variable_size) +{ + return EFI_UNSUPPORTED; +} + static efi_status_t gsmi_set_variable(efi_char16_t *name, efi_guid_t *vendor, u32 attr, @@ -473,6 +481,7 @@ static const struct efivar_operations efivar_ops = { .get_variable = gsmi_get_variable, .set_variable = gsmi_set_variable, .get_next_variable = gsmi_get_next_variable, + .query_variable_info = gsmi_query_variable_info, }; static ssize_t eventlog_write(struct file *filp, struct kobject *kobj, diff --git a/include/linux/efi.h b/include/linux/efi.h index 2362a0b..f3f3860 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -446,6 +446,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_info_t *query_variable_info; }; struct efivars { -- 1.7.7.1 ^ permalink raw reply related [flat|nested] 20+ messages in thread
end of thread, other threads:[~2011-12-15 15:48 UTC | newest] Thread overview: 20+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2011-12-14 21:06 Update UEFI variable support Matthew Garrett 2011-12-14 21:06 ` [PATCH 1/4] uefi: Populate runtime_version Matthew Garrett 2011-12-14 21:06 ` [PATCH 2/4] efi: Only create sysfs entries while booting or running Matthew Garrett 2011-12-14 21:32 ` Mike Waychison 2011-12-14 21:06 ` [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes Matthew Garrett 2011-12-14 22:14 ` Mike Waychison 2011-12-14 22:39 ` Matthew Garrett 2011-12-14 22:44 ` H. Peter Anvin 2011-12-14 22:57 ` Matthew Garrett 2011-12-14 22:58 ` H. Peter Anvin 2011-12-15 15:44 ` Matthew Garrett 2011-12-14 22:57 ` Mike Waychison 2011-12-14 23:00 ` H. Peter Anvin 2011-12-14 23:06 ` Mike Waychison 2011-12-14 23:22 ` H. Peter Anvin 2011-12-14 23:24 ` Mike Waychison 2011-12-15 15:43 ` Matthew Garrett 2011-12-15 15:46 ` H. Peter Anvin 2011-12-15 15:48 ` Matthew Garrett 2011-12-14 21:06 ` [PATCH 4/4] EFI: Add support for QueryVariableInfo() call Matthew Garrett
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox