Merge branch 'pstore-efi' of git://git.kernel.org/pub/scm/linux/kernel/git/aegl/linux-2.6
* 'pstore-efi' of git://git.kernel.org/pub/scm/linux/kernel/git/aegl/linux-2.6: efivars: Introduce PSTORE_EFI_ATTRIBUTES efivars: Use string functions in pstore_write efivars: introduce utf16_strncmp efivars: String functions efi: Add support for using efivars as a pstore backend pstore: Allow the user to explicitly choose a backend pstore: Make "part" unsigned pstore: Add extra context for writes and erases pstore: Extend API for more flexibility in new backends
This commit is contained in:
+235
-16
@@ -78,6 +78,7 @@
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pstore.h>
|
||||
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
@@ -89,6 +90,8 @@ MODULE_DESCRIPTION("sysfs interface to EFI Variables");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(EFIVARS_VERSION);
|
||||
|
||||
#define DUMP_NAME_LEN 52
|
||||
|
||||
/*
|
||||
* The maximum size of VariableName + Data = 1024
|
||||
* Therefore, it's reasonable to save that much
|
||||
@@ -119,6 +122,10 @@ struct efivar_attribute {
|
||||
ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count);
|
||||
};
|
||||
|
||||
#define PSTORE_EFI_ATTRIBUTES \
|
||||
(EFI_VARIABLE_NON_VOLATILE | \
|
||||
EFI_VARIABLE_BOOTSERVICE_ACCESS | \
|
||||
EFI_VARIABLE_RUNTIME_ACCESS)
|
||||
|
||||
#define EFIVAR_ATTR(_name, _mode, _show, _store) \
|
||||
struct efivar_attribute efivar_attr_##_name = { \
|
||||
@@ -141,23 +148,61 @@ efivar_create_sysfs_entry(struct efivars *efivars,
|
||||
|
||||
/* Return the number of unicode characters in data */
|
||||
static unsigned long
|
||||
utf8_strlen(efi_char16_t *data, unsigned long maxlength)
|
||||
utf16_strnlen(efi_char16_t *s, size_t maxlength)
|
||||
{
|
||||
unsigned long length = 0;
|
||||
|
||||
while (*data++ != 0 && length < maxlength)
|
||||
while (*s++ != 0 && length < maxlength)
|
||||
length++;
|
||||
return length;
|
||||
}
|
||||
|
||||
static 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
|
||||
utf8_strsize(efi_char16_t *data, unsigned long maxlength)
|
||||
utf16_strsize(efi_char16_t *data, unsigned long maxlength)
|
||||
{
|
||||
return utf8_strlen(data, maxlength/sizeof(efi_char16_t)) * sizeof(efi_char16_t);
|
||||
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 efi_status_t
|
||||
get_var_data_locked(struct efivars *efivars, struct efi_variable *var)
|
||||
{
|
||||
efi_status_t status;
|
||||
|
||||
var->DataSize = 1024;
|
||||
status = efivars->ops->get_variable(var->VariableName,
|
||||
&var->VendorGuid,
|
||||
&var->Attributes,
|
||||
&var->DataSize,
|
||||
var->Data);
|
||||
return status;
|
||||
}
|
||||
|
||||
static efi_status_t
|
||||
@@ -166,13 +211,9 @@ get_var_data(struct efivars *efivars, struct efi_variable *var)
|
||||
efi_status_t status;
|
||||
|
||||
spin_lock(&efivars->lock);
|
||||
var->DataSize = 1024;
|
||||
status = efivars->ops->get_variable(var->VariableName,
|
||||
&var->VendorGuid,
|
||||
&var->Attributes,
|
||||
&var->DataSize,
|
||||
var->Data);
|
||||
status = get_var_data_locked(efivars, var);
|
||||
spin_unlock(&efivars->lock);
|
||||
|
||||
if (status != EFI_SUCCESS) {
|
||||
printk(KERN_WARNING "efivars: get_variable() failed 0x%lx!\n",
|
||||
status);
|
||||
@@ -387,12 +428,180 @@ static struct kobj_type efivar_ktype = {
|
||||
.default_attrs = def_attrs,
|
||||
};
|
||||
|
||||
static struct pstore_info efi_pstore_info;
|
||||
|
||||
static inline void
|
||||
efivar_unregister(struct efivar_entry *var)
|
||||
{
|
||||
kobject_put(&var->kobj);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PSTORE
|
||||
|
||||
static int efi_pstore_open(struct pstore_info *psi)
|
||||
{
|
||||
struct efivars *efivars = psi->data;
|
||||
|
||||
spin_lock(&efivars->lock);
|
||||
efivars->walk_entry = list_first_entry(&efivars->list,
|
||||
struct efivar_entry, list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int efi_pstore_close(struct pstore_info *psi)
|
||||
{
|
||||
struct efivars *efivars = psi->data;
|
||||
|
||||
spin_unlock(&efivars->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
|
||||
struct timespec *timespec, struct pstore_info *psi)
|
||||
{
|
||||
efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
|
||||
struct efivars *efivars = psi->data;
|
||||
char name[DUMP_NAME_LEN];
|
||||
int i;
|
||||
unsigned int part, size;
|
||||
unsigned long time;
|
||||
|
||||
while (&efivars->walk_entry->list != &efivars->list) {
|
||||
if (!efi_guidcmp(efivars->walk_entry->var.VendorGuid,
|
||||
vendor)) {
|
||||
for (i = 0; i < DUMP_NAME_LEN; i++) {
|
||||
name[i] = efivars->walk_entry->var.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;
|
||||
memcpy(psi->buf, efivars->walk_entry->var.Data, size);
|
||||
efivars->walk_entry = list_entry(efivars->walk_entry->list.next,
|
||||
struct efivar_entry, list);
|
||||
return size;
|
||||
}
|
||||
}
|
||||
efivars->walk_entry = list_entry(efivars->walk_entry->list.next,
|
||||
struct efivar_entry, list);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u64 efi_pstore_write(enum pstore_type_id type, unsigned int part,
|
||||
size_t size, struct pstore_info *psi)
|
||||
{
|
||||
char name[DUMP_NAME_LEN];
|
||||
char stub_name[DUMP_NAME_LEN];
|
||||
efi_char16_t efi_name[DUMP_NAME_LEN];
|
||||
efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
|
||||
struct efivars *efivars = psi->data;
|
||||
struct efivar_entry *entry, *found = NULL;
|
||||
int i;
|
||||
|
||||
sprintf(stub_name, "dump-type%u-%u-", type, part);
|
||||
sprintf(name, "%s%lu", stub_name, get_seconds());
|
||||
|
||||
spin_lock(&efivars->lock);
|
||||
|
||||
for (i = 0; i < DUMP_NAME_LEN; i++)
|
||||
efi_name[i] = stub_name[i];
|
||||
|
||||
/*
|
||||
* Clean up any entries with the same name
|
||||
*/
|
||||
|
||||
list_for_each_entry(entry, &efivars->list, list) {
|
||||
get_var_data_locked(efivars, &entry->var);
|
||||
|
||||
if (efi_guidcmp(entry->var.VendorGuid, vendor))
|
||||
continue;
|
||||
if (utf16_strncmp(entry->var.VariableName, efi_name,
|
||||
utf16_strlen(efi_name)))
|
||||
continue;
|
||||
/* Needs to be a prefix */
|
||||
if (entry->var.VariableName[utf16_strlen(efi_name)] == 0)
|
||||
continue;
|
||||
|
||||
/* found */
|
||||
found = entry;
|
||||
efivars->ops->set_variable(entry->var.VariableName,
|
||||
&entry->var.VendorGuid,
|
||||
PSTORE_EFI_ATTRIBUTES,
|
||||
0, NULL);
|
||||
}
|
||||
|
||||
if (found)
|
||||
list_del(&found->list);
|
||||
|
||||
for (i = 0; i < DUMP_NAME_LEN; i++)
|
||||
efi_name[i] = name[i];
|
||||
|
||||
efivars->ops->set_variable(efi_name, &vendor, PSTORE_EFI_ATTRIBUTES,
|
||||
size, psi->buf);
|
||||
|
||||
spin_unlock(&efivars->lock);
|
||||
|
||||
if (found)
|
||||
efivar_unregister(found);
|
||||
|
||||
if (size)
|
||||
efivar_create_sysfs_entry(efivars,
|
||||
utf16_strsize(efi_name,
|
||||
DUMP_NAME_LEN * 2),
|
||||
efi_name, &vendor);
|
||||
|
||||
return part;
|
||||
};
|
||||
|
||||
static int efi_pstore_erase(enum pstore_type_id type, u64 id,
|
||||
struct pstore_info *psi)
|
||||
{
|
||||
efi_pstore_write(type, id, 0, psi);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int efi_pstore_open(struct pstore_info *psi)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int efi_pstore_close(struct pstore_info *psi)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
|
||||
struct timespec *time, struct pstore_info *psi)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static u64 efi_pstore_write(enum pstore_type_id type, int part, size_t size,
|
||||
struct pstore_info *psi)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int efi_pstore_erase(enum pstore_type_id type, u64 id,
|
||||
struct pstore_info *psi)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct pstore_info efi_pstore_info = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "efi",
|
||||
.open = efi_pstore_open,
|
||||
.close = efi_pstore_close,
|
||||
.read = efi_pstore_read,
|
||||
.write = efi_pstore_write,
|
||||
.erase = efi_pstore_erase,
|
||||
};
|
||||
|
||||
static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
@@ -414,8 +623,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 = utf8_strsize(search_efivar->var.VariableName, 1024);
|
||||
strsize2 = utf8_strsize(new_var->VariableName, 1024);
|
||||
strsize1 = utf16_strsize(search_efivar->var.VariableName, 1024);
|
||||
strsize2 = utf16_strsize(new_var->VariableName, 1024);
|
||||
if (strsize1 == strsize2 &&
|
||||
!memcmp(&(search_efivar->var.VariableName),
|
||||
new_var->VariableName, strsize1) &&
|
||||
@@ -447,8 +656,8 @@ 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,
|
||||
utf8_strsize(new_var->VariableName,
|
||||
1024),
|
||||
utf16_strsize(new_var->VariableName,
|
||||
1024),
|
||||
new_var->VariableName,
|
||||
&new_var->VendorGuid);
|
||||
if (status) {
|
||||
@@ -477,8 +686,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 = utf8_strsize(search_efivar->var.VariableName, 1024);
|
||||
strsize2 = utf8_strsize(del_var->VariableName, 1024);
|
||||
strsize1 = utf16_strsize(search_efivar->var.VariableName, 1024);
|
||||
strsize2 = utf16_strsize(del_var->VariableName, 1024);
|
||||
if (strsize1 == strsize2 &&
|
||||
!memcmp(&(search_efivar->var.VariableName),
|
||||
del_var->VariableName, strsize1) &&
|
||||
@@ -763,6 +972,16 @@ int register_efivars(struct efivars *efivars,
|
||||
if (error)
|
||||
unregister_efivars(efivars);
|
||||
|
||||
efivars->efi_pstore_info = efi_pstore_info;
|
||||
|
||||
efivars->efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL);
|
||||
if (efivars->efi_pstore_info.buf) {
|
||||
efivars->efi_pstore_info.bufsize = 1024;
|
||||
efivars->efi_pstore_info.data = efivars;
|
||||
mutex_init(&efivars->efi_pstore_info.buf_mutex);
|
||||
pstore_register(&efivars->efi_pstore_info);
|
||||
}
|
||||
|
||||
out:
|
||||
kfree(variable_name);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user