|
@@ -78,6 +78,7 @@
|
|
#include <linux/kobject.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/device.h>
|
|
#include <linux/device.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/slab.h>
|
|
|
|
+#include <linux/pstore.h>
|
|
|
|
|
|
#include <asm/uaccess.h>
|
|
#include <asm/uaccess.h>
|
|
|
|
|
|
@@ -89,6 +90,8 @@ MODULE_DESCRIPTION("sysfs interface to EFI Variables");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_VERSION(EFIVARS_VERSION);
|
|
MODULE_VERSION(EFIVARS_VERSION);
|
|
|
|
|
|
|
|
+#define DUMP_NAME_LEN 52
|
|
|
|
+
|
|
/*
|
|
/*
|
|
* The maximum size of VariableName + Data = 1024
|
|
* The maximum size of VariableName + Data = 1024
|
|
* Therefore, it's reasonable to save that much
|
|
* 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);
|
|
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) \
|
|
#define EFIVAR_ATTR(_name, _mode, _show, _store) \
|
|
struct efivar_attribute efivar_attr_##_name = { \
|
|
struct efivar_attribute efivar_attr_##_name = { \
|
|
@@ -141,38 +148,72 @@ efivar_create_sysfs_entry(struct efivars *efivars,
|
|
|
|
|
|
/* Return the number of unicode characters in data */
|
|
/* Return the number of unicode characters in data */
|
|
static unsigned long
|
|
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;
|
|
unsigned long length = 0;
|
|
|
|
|
|
- while (*data++ != 0 && length < maxlength)
|
|
|
|
|
|
+ while (*s++ != 0 && length < maxlength)
|
|
length++;
|
|
length++;
|
|
return 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
|
|
* Return the number of bytes is the length of this string
|
|
* Note: this is NOT the same as the number of unicode characters
|
|
* Note: this is NOT the same as the number of unicode characters
|
|
*/
|
|
*/
|
|
static inline unsigned long
|
|
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
|
|
static efi_status_t
|
|
-get_var_data(struct efivars *efivars, struct efi_variable *var)
|
|
|
|
|
|
+get_var_data_locked(struct efivars *efivars, struct efi_variable *var)
|
|
{
|
|
{
|
|
efi_status_t status;
|
|
efi_status_t status;
|
|
|
|
|
|
- spin_lock(&efivars->lock);
|
|
|
|
var->DataSize = 1024;
|
|
var->DataSize = 1024;
|
|
status = efivars->ops->get_variable(var->VariableName,
|
|
status = efivars->ops->get_variable(var->VariableName,
|
|
&var->VendorGuid,
|
|
&var->VendorGuid,
|
|
&var->Attributes,
|
|
&var->Attributes,
|
|
&var->DataSize,
|
|
&var->DataSize,
|
|
var->Data);
|
|
var->Data);
|
|
|
|
+ return status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static efi_status_t
|
|
|
|
+get_var_data(struct efivars *efivars, struct efi_variable *var)
|
|
|
|
+{
|
|
|
|
+ efi_status_t status;
|
|
|
|
+
|
|
|
|
+ spin_lock(&efivars->lock);
|
|
|
|
+ status = get_var_data_locked(efivars, var);
|
|
spin_unlock(&efivars->lock);
|
|
spin_unlock(&efivars->lock);
|
|
|
|
+
|
|
if (status != EFI_SUCCESS) {
|
|
if (status != EFI_SUCCESS) {
|
|
printk(KERN_WARNING "efivars: get_variable() failed 0x%lx!\n",
|
|
printk(KERN_WARNING "efivars: get_variable() failed 0x%lx!\n",
|
|
status);
|
|
status);
|
|
@@ -387,12 +428,180 @@ static struct kobj_type efivar_ktype = {
|
|
.default_attrs = def_attrs,
|
|
.default_attrs = def_attrs,
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+static struct pstore_info efi_pstore_info;
|
|
|
|
+
|
|
static inline void
|
|
static inline void
|
|
efivar_unregister(struct efivar_entry *var)
|
|
efivar_unregister(struct efivar_entry *var)
|
|
{
|
|
{
|
|
kobject_put(&var->kobj);
|
|
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,
|
|
static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
|
|
struct bin_attribute *bin_attr,
|
|
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?
|
|
* Does this variable already exist?
|
|
*/
|
|
*/
|
|
list_for_each_entry_safe(search_efivar, n, &efivars->list, list) {
|
|
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 &&
|
|
if (strsize1 == strsize2 &&
|
|
!memcmp(&(search_efivar->var.VariableName),
|
|
!memcmp(&(search_efivar->var.VariableName),
|
|
new_var->VariableName, strsize1) &&
|
|
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 */
|
|
/* Create the entry in sysfs. Locking is not required here */
|
|
status = efivar_create_sysfs_entry(efivars,
|
|
status = efivar_create_sysfs_entry(efivars,
|
|
- utf8_strsize(new_var->VariableName,
|
|
|
|
- 1024),
|
|
|
|
|
|
+ utf16_strsize(new_var->VariableName,
|
|
|
|
+ 1024),
|
|
new_var->VariableName,
|
|
new_var->VariableName,
|
|
&new_var->VendorGuid);
|
|
&new_var->VendorGuid);
|
|
if (status) {
|
|
if (status) {
|
|
@@ -477,8 +686,8 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
|
|
* Does this variable already exist?
|
|
* Does this variable already exist?
|
|
*/
|
|
*/
|
|
list_for_each_entry_safe(search_efivar, n, &efivars->list, list) {
|
|
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 &&
|
|
if (strsize1 == strsize2 &&
|
|
!memcmp(&(search_efivar->var.VariableName),
|
|
!memcmp(&(search_efivar->var.VariableName),
|
|
del_var->VariableName, strsize1) &&
|
|
del_var->VariableName, strsize1) &&
|
|
@@ -763,6 +972,16 @@ int register_efivars(struct efivars *efivars,
|
|
if (error)
|
|
if (error)
|
|
unregister_efivars(efivars);
|
|
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:
|
|
out:
|
|
kfree(variable_name);
|
|
kfree(variable_name);
|
|
|
|
|