efi: pstore: Omit efivars caching EFI varstore access layer

Avoid the efivars layer and simply call the newly introduced EFI
varstore helpers instead. This simplifies the code substantially, and
also allows us to remove some hacks in the shared efivars layer that
were added for efi-pstore specifically.

In order to be able to delete the EFI variable associated with a record,
store the UTF-16 name of the variable in the pstore record's priv field.
That way, we don't have to make guesses regarding which variable the
record may have been loaded from.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
This commit is contained in:
Ard Biesheuvel 2022-06-20 13:21:26 +02:00
parent 472831d4c4
commit 859748255b
5 changed files with 99 additions and 318 deletions

View File

@ -22,6 +22,7 @@ config EFI_ESRT
config EFI_VARS_PSTORE
tristate "Register efivars backend for pstore"
depends on PSTORE
select UCS2_STRING
default y
help
Say Y here to enable use efivars as a backend to pstore. This

View File

@ -6,6 +6,8 @@
#include <linux/slab.h>
#include <linux/ucs2_string.h>
MODULE_IMPORT_NS(EFIVAR);
#define DUMP_NAME_LEN 66
#define EFIVARS_DATA_SIZE_MAX 1024
@ -20,18 +22,25 @@ module_param_named(pstore_disable, efivars_pstore_disable, bool, 0644);
EFI_VARIABLE_BOOTSERVICE_ACCESS | \
EFI_VARIABLE_RUNTIME_ACCESS)
static LIST_HEAD(efi_pstore_list);
static DECLARE_WORK(efivar_work, NULL);
static int efi_pstore_open(struct pstore_info *psi)
{
psi->data = NULL;
int err;
err = efivar_lock();
if (err)
return err;
psi->data = kzalloc(EFIVARS_DATA_SIZE_MAX, GFP_KERNEL);
if (!psi->data)
return -ENOMEM;
return 0;
}
static int efi_pstore_close(struct pstore_info *psi)
{
psi->data = NULL;
efivar_unlock();
kfree(psi->data);
return 0;
}
@ -40,22 +49,17 @@ static inline u64 generic_id(u64 timestamp, unsigned int part, int count)
return (timestamp * 100 + part) * 1000 + count;
}
static int efi_pstore_read_func(struct efivar_entry *entry,
struct pstore_record *record)
static int efi_pstore_read_func(struct pstore_record *record,
efi_char16_t *varname)
{
efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
unsigned long wlen, size = EFIVARS_DATA_SIZE_MAX;
char name[DUMP_NAME_LEN], data_type;
int i;
efi_status_t status;
int cnt;
unsigned int part;
unsigned long size;
u64 time;
if (efi_guidcmp(entry->var.VendorGuid, vendor))
return 0;
for (i = 0; i < DUMP_NAME_LEN; i++)
name[i] = entry->var.VariableName[i];
ucs2_as_utf8(name, varname, DUMP_NAME_LEN);
if (sscanf(name, "dump-type%u-%u-%d-%llu-%c",
&record->type, &part, &cnt, &time, &data_type) == 5) {
@ -95,161 +99,75 @@ static int efi_pstore_read_func(struct efivar_entry *entry,
} else
return 0;
entry->var.DataSize = 1024;
__efivar_entry_get(entry, &entry->var.Attributes,
&entry->var.DataSize, entry->var.Data);
size = entry->var.DataSize;
memcpy(record->buf, entry->var.Data,
(size_t)min_t(unsigned long, EFIVARS_DATA_SIZE_MAX, size));
return size;
}
/**
* efi_pstore_scan_sysfs_enter
* @pos: scanning entry
* @next: next entry
* @head: list head
*/
static void efi_pstore_scan_sysfs_enter(struct efivar_entry *pos,
struct efivar_entry *next,
struct list_head *head)
{
pos->scanning = true;
if (&next->list != head)
next->scanning = true;
}
/**
* __efi_pstore_scan_sysfs_exit
* @entry: deleting entry
* @turn_off_scanning: Check if a scanning flag should be turned off
*/
static inline int __efi_pstore_scan_sysfs_exit(struct efivar_entry *entry,
bool turn_off_scanning)
{
if (entry->deleting) {
list_del(&entry->list);
efivar_entry_iter_end();
kfree(entry);
if (efivar_entry_iter_begin())
return -EINTR;
} else if (turn_off_scanning)
entry->scanning = false;
return 0;
}
/**
* efi_pstore_scan_sysfs_exit
* @pos: scanning entry
* @next: next entry
* @head: list head
* @stop: a flag checking if scanning will stop
*/
static int efi_pstore_scan_sysfs_exit(struct efivar_entry *pos,
struct efivar_entry *next,
struct list_head *head, bool stop)
{
int ret = __efi_pstore_scan_sysfs_exit(pos, true);
if (ret)
return ret;
if (stop)
ret = __efi_pstore_scan_sysfs_exit(next, &next->list != head);
return ret;
}
/**
* efi_pstore_sysfs_entry_iter
*
* @record: pstore record to pass to callback
*
* You MUST call efivar_entry_iter_begin() before this function, and
* efivar_entry_iter_end() afterwards.
*
*/
static int efi_pstore_sysfs_entry_iter(struct pstore_record *record)
{
struct efivar_entry **pos = (struct efivar_entry **)&record->psi->data;
struct efivar_entry *entry, *n;
struct list_head *head = &efi_pstore_list;
int size = 0;
int ret;
if (!*pos) {
list_for_each_entry_safe(entry, n, head, list) {
efi_pstore_scan_sysfs_enter(entry, n, head);
size = efi_pstore_read_func(entry, record);
ret = efi_pstore_scan_sysfs_exit(entry, n, head,
size < 0);
if (ret)
return ret;
if (size)
break;
}
*pos = n;
return size;
}
list_for_each_entry_safe_from((*pos), n, head, list) {
efi_pstore_scan_sysfs_enter((*pos), n, head);
size = efi_pstore_read_func((*pos), record);
ret = efi_pstore_scan_sysfs_exit((*pos), n, head, size < 0);
if (ret)
return ret;
if (size)
break;
}
*pos = n;
return size;
}
/**
* efi_pstore_read
*
* This function returns a size of NVRAM entry logged via efi_pstore_write().
* The meaning and behavior of efi_pstore/pstore are as below.
*
* size > 0: Got data of an entry logged via efi_pstore_write() successfully,
* and pstore filesystem will continue reading subsequent entries.
* size == 0: Entry was not logged via efi_pstore_write(),
* and efi_pstore driver will continue reading subsequent entries.
* size < 0: Failed to get data of entry logging via efi_pstore_write(),
* and pstore will stop reading entry.
*/
static ssize_t efi_pstore_read(struct pstore_record *record)
{
ssize_t size;
record->buf = kzalloc(EFIVARS_DATA_SIZE_MAX, GFP_KERNEL);
record->buf = kmalloc(size, GFP_KERNEL);
if (!record->buf)
return -ENOMEM;
if (efivar_entry_iter_begin()) {
size = -EINTR;
goto out;
}
size = efi_pstore_sysfs_entry_iter(record);
efivar_entry_iter_end();
out:
if (size <= 0) {
status = efivar_get_variable(varname, &LINUX_EFI_CRASH_GUID, NULL,
&size, record->buf);
if (status != EFI_SUCCESS) {
kfree(record->buf);
record->buf = NULL;
return -EIO;
}
/*
* Store the name of the variable in the pstore_record priv field, so
* we can reuse it later if we need to delete the EFI variable from the
* variable store.
*/
wlen = (ucs2_strnlen(varname, DUMP_NAME_LEN) + 1) * sizeof(efi_char16_t);
record->priv = kmemdup(varname, wlen, GFP_KERNEL);
if (!record->priv) {
kfree(record->buf);
return -ENOMEM;
}
return size;
}
static ssize_t efi_pstore_read(struct pstore_record *record)
{
efi_char16_t *varname = record->psi->data;
efi_guid_t guid = LINUX_EFI_CRASH_GUID;
unsigned long varname_size;
efi_status_t status;
for (;;) {
varname_size = EFIVARS_DATA_SIZE_MAX;
/*
* If this is the first read() call in the pstore enumeration,
* varname will be the empty string, and the GetNextVariable()
* runtime service call will return the first EFI variable in
* its own enumeration order, ignoring the guid argument.
*
* Subsequent calls to GetNextVariable() must pass the name and
* guid values returned by the previous call, which is why we
* store varname in record->psi->data. Given that we only
* enumerate variables with the efi-pstore GUID, there is no
* need to record the guid return value.
*/
status = efivar_get_next_variable(&varname_size, varname, &guid);
if (status == EFI_NOT_FOUND)
return 0;
if (status != EFI_SUCCESS)
return -EIO;
/* skip variables that don't concern us */
if (efi_guidcmp(guid, LINUX_EFI_CRASH_GUID))
continue;
return efi_pstore_read_func(record, varname);
}
}
static int efi_pstore_write(struct pstore_record *record)
{
char name[DUMP_NAME_LEN];
efi_char16_t efi_name[DUMP_NAME_LEN];
efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
int i, ret = 0;
efi_status_t status;
int i;
record->id = generic_id(record->time.tv_sec, record->part,
record->count);
@ -265,88 +183,26 @@ static int efi_pstore_write(struct pstore_record *record)
for (i = 0; i < DUMP_NAME_LEN; i++)
efi_name[i] = name[i];
ret = efivar_entry_set_safe(efi_name, vendor, PSTORE_EFI_ATTRIBUTES,
false, record->size, record->psi->buf);
if (record->reason == KMSG_DUMP_OOPS && try_module_get(THIS_MODULE))
if (!schedule_work(&efivar_work))
module_put(THIS_MODULE);
return ret;
if (efivar_trylock())
return -EBUSY;
status = efivar_set_variable_locked(efi_name, &LINUX_EFI_CRASH_GUID,
PSTORE_EFI_ATTRIBUTES,
record->size, record->psi->buf,
true);
efivar_unlock();
return status == EFI_SUCCESS ? 0 : -EIO;
};
/*
* Clean up an entry with the same name
*/
static int efi_pstore_erase_func(struct efivar_entry *entry, void *data)
{
efi_char16_t *efi_name = data;
efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
unsigned long ucs2_len = ucs2_strlen(efi_name);
if (efi_guidcmp(entry->var.VendorGuid, vendor))
return 0;
if (ucs2_strncmp(entry->var.VariableName, efi_name, (size_t)ucs2_len))
return 0;
if (entry->scanning) {
/*
* Skip deletion because this entry will be deleted
* after scanning is completed.
*/
entry->deleting = true;
} else
list_del(&entry->list);
/* found */
__efivar_entry_delete(entry);
return 1;
}
static int efi_pstore_erase_name(const char *name)
{
struct efivar_entry *entry = NULL;
efi_char16_t efi_name[DUMP_NAME_LEN];
int found, i;
for (i = 0; i < DUMP_NAME_LEN; i++) {
efi_name[i] = name[i];
if (name[i] == '\0')
break;
}
if (efivar_entry_iter_begin())
return -EINTR;
found = __efivar_entry_iter(efi_pstore_erase_func, &efi_pstore_list,
efi_name, &entry);
efivar_entry_iter_end();
if (found && !entry->scanning)
kfree(entry);
return found ? 0 : -ENOENT;
}
static int efi_pstore_erase(struct pstore_record *record)
{
char name[DUMP_NAME_LEN];
int ret;
efi_status_t status;
snprintf(name, sizeof(name), "dump-type%u-%u-%d-%lld",
record->type, record->part, record->count,
(long long)record->time.tv_sec);
ret = efi_pstore_erase_name(name);
if (ret != -ENOENT)
return ret;
status = efivar_set_variable(record->priv, &LINUX_EFI_CRASH_GUID,
PSTORE_EFI_ATTRIBUTES, 0, NULL);
snprintf(name, sizeof(name), "dump-type%u-%u-%lld",
record->type, record->part, (long long)record->time.tv_sec);
ret = efi_pstore_erase_name(name);
return ret;
if (status != EFI_SUCCESS && status != EFI_NOT_FOUND)
return -EIO;
return 0;
}
static struct pstore_info efi_pstore_info = {
@ -360,74 +216,14 @@ static struct pstore_info efi_pstore_info = {
.erase = efi_pstore_erase,
};
static int efi_pstore_callback(efi_char16_t *name, efi_guid_t vendor,
unsigned long name_size, void *data)
{
struct efivar_entry *entry;
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry)
return -ENOMEM;
memcpy(entry->var.VariableName, name, name_size);
entry->var.VendorGuid = vendor;
__efivar_entry_add(entry, &efi_pstore_list);
return 0;
}
static int efi_pstore_update_entry(efi_char16_t *name, efi_guid_t vendor,
unsigned long name_size, void *data)
{
struct efivar_entry *entry = data;
if (efivar_entry_find(name, vendor, &efi_pstore_list, false))
return 0;
memcpy(entry->var.VariableName, name, name_size);
memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
return 1;
}
static void efi_pstore_update_entries(struct work_struct *work)
{
struct efivar_entry *entry;
int err;
/* Add new sysfs entries */
while (1) {
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry)
return;
err = efivar_init(efi_pstore_update_entry, entry,
false, &efi_pstore_list);
if (!err)
break;
efivar_entry_add(entry, &efi_pstore_list);
}
kfree(entry);
module_put(THIS_MODULE);
}
static __init int efivars_pstore_init(void)
{
int ret;
if (!efivars_kobject() || !efivar_supports_writes())
if (!efivar_supports_writes())
return 0;
if (efivars_pstore_disable)
return 0;
ret = efivar_init(efi_pstore_callback, NULL, true, &efi_pstore_list);
if (ret)
return ret;
efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL);
if (!efi_pstore_info.buf)
return -ENOMEM;
@ -440,8 +236,6 @@ static __init int efivars_pstore_init(void)
efi_pstore_info.bufsize = 0;
}
INIT_WORK(&efivar_work, efi_pstore_update_entries);
return 0;
}

View File

@ -467,16 +467,12 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
else if (__efivar_entry_delete(entry))
err = -EIO;
if (err) {
efivar_entry_iter_end();
return err;
}
efivar_entry_iter_end();
if (!entry->scanning) {
efivar_entry_iter_end();
efivar_unregister(entry);
} else
efivar_entry_iter_end();
if (err)
return err;
efivar_unregister(entry);
/* It's dead Jim.... */
return count;

View File

@ -821,16 +821,8 @@ struct efivar_entry *efivar_entry_find(efi_char16_t *name, efi_guid_t guid,
if (!found)
return NULL;
if (remove) {
if (entry->scanning) {
/*
* The entry will be deleted
* after scanning is completed.
*/
entry->deleting = true;
} else
list_del(&entry->list);
}
if (remove)
list_del(&entry->list);
return entry;
}

View File

@ -1043,8 +1043,6 @@ struct efivar_entry {
struct efi_variable var;
struct list_head list;
struct kobject kobj;
bool scanning;
bool deleting;
};
static inline void