diff options
author | Matthew Garrett <mjg@redhat.com> | 2011-07-21 16:57:56 -0400 |
---|---|---|
committer | Tony Luck <tony.luck@intel.com> | 2011-07-22 19:15:04 -0400 |
commit | 5ee9c198a4208d7760275d48e4c4f6c89dcd2ef0 (patch) | |
tree | 19f0c26f13df5302432afd1e870f357fa009b990 /drivers/firmware/efivars.c | |
parent | dee28e72b619b48ec80a9e5509db458dbe66f71f (diff) |
efi: Add support for using efivars as a pstore backend
EFI provides an area of nonvolatile storage managed by the firmware. We
can use this as a pstore backend to maintain copies of oopses, aiding
diagnosis.
Signed-off-by: Matthew Garrett <mjg@redhat.com>
Signed-off-by: Tony Luck <tony.luck@intel.com>
Diffstat (limited to 'drivers/firmware/efivars.c')
-rw-r--r-- | drivers/firmware/efivars.c | 191 |
1 files changed, 189 insertions, 2 deletions
diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index 5f29aafd4462..2bbb22670d2d 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c | |||
@@ -78,6 +78,7 @@ | |||
78 | #include <linux/kobject.h> | 78 | #include <linux/kobject.h> |
79 | #include <linux/device.h> | 79 | #include <linux/device.h> |
80 | #include <linux/slab.h> | 80 | #include <linux/slab.h> |
81 | #include <linux/pstore.h> | ||
81 | 82 | ||
82 | #include <asm/uaccess.h> | 83 | #include <asm/uaccess.h> |
83 | 84 | ||
@@ -89,6 +90,8 @@ MODULE_DESCRIPTION("sysfs interface to EFI Variables"); | |||
89 | MODULE_LICENSE("GPL"); | 90 | MODULE_LICENSE("GPL"); |
90 | MODULE_VERSION(EFIVARS_VERSION); | 91 | MODULE_VERSION(EFIVARS_VERSION); |
91 | 92 | ||
93 | #define DUMP_NAME_LEN 52 | ||
94 | |||
92 | /* | 95 | /* |
93 | * The maximum size of VariableName + Data = 1024 | 96 | * The maximum size of VariableName + Data = 1024 |
94 | * Therefore, it's reasonable to save that much | 97 | * Therefore, it's reasonable to save that much |
@@ -161,18 +164,28 @@ utf8_strsize(efi_char16_t *data, unsigned long maxlength) | |||
161 | } | 164 | } |
162 | 165 | ||
163 | static efi_status_t | 166 | static efi_status_t |
164 | get_var_data(struct efivars *efivars, struct efi_variable *var) | 167 | get_var_data_locked(struct efivars *efivars, struct efi_variable *var) |
165 | { | 168 | { |
166 | efi_status_t status; | 169 | efi_status_t status; |
167 | 170 | ||
168 | spin_lock(&efivars->lock); | ||
169 | var->DataSize = 1024; | 171 | var->DataSize = 1024; |
170 | status = efivars->ops->get_variable(var->VariableName, | 172 | status = efivars->ops->get_variable(var->VariableName, |
171 | &var->VendorGuid, | 173 | &var->VendorGuid, |
172 | &var->Attributes, | 174 | &var->Attributes, |
173 | &var->DataSize, | 175 | &var->DataSize, |
174 | var->Data); | 176 | var->Data); |
177 | return status; | ||
178 | } | ||
179 | |||
180 | static efi_status_t | ||
181 | get_var_data(struct efivars *efivars, struct efi_variable *var) | ||
182 | { | ||
183 | efi_status_t status; | ||
184 | |||
185 | spin_lock(&efivars->lock); | ||
186 | status = get_var_data_locked(efivars, var); | ||
175 | spin_unlock(&efivars->lock); | 187 | spin_unlock(&efivars->lock); |
188 | |||
176 | if (status != EFI_SUCCESS) { | 189 | if (status != EFI_SUCCESS) { |
177 | printk(KERN_WARNING "efivars: get_variable() failed 0x%lx!\n", | 190 | printk(KERN_WARNING "efivars: get_variable() failed 0x%lx!\n", |
178 | status); | 191 | status); |
@@ -387,12 +400,176 @@ static struct kobj_type efivar_ktype = { | |||
387 | .default_attrs = def_attrs, | 400 | .default_attrs = def_attrs, |
388 | }; | 401 | }; |
389 | 402 | ||
403 | static struct pstore_info efi_pstore_info; | ||
404 | |||
390 | static inline void | 405 | static inline void |
391 | efivar_unregister(struct efivar_entry *var) | 406 | efivar_unregister(struct efivar_entry *var) |
392 | { | 407 | { |
393 | kobject_put(&var->kobj); | 408 | kobject_put(&var->kobj); |
394 | } | 409 | } |
395 | 410 | ||
411 | #ifdef CONFIG_PSTORE | ||
412 | |||
413 | static int efi_pstore_open(struct pstore_info *psi) | ||
414 | { | ||
415 | struct efivars *efivars = psi->data; | ||
416 | |||
417 | spin_lock(&efivars->lock); | ||
418 | efivars->walk_entry = list_first_entry(&efivars->list, | ||
419 | struct efivar_entry, list); | ||
420 | return 0; | ||
421 | } | ||
422 | |||
423 | static int efi_pstore_close(struct pstore_info *psi) | ||
424 | { | ||
425 | struct efivars *efivars = psi->data; | ||
426 | |||
427 | spin_unlock(&efivars->lock); | ||
428 | return 0; | ||
429 | } | ||
430 | |||
431 | static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type, | ||
432 | struct timespec *timespec, struct pstore_info *psi) | ||
433 | { | ||
434 | efi_guid_t vendor = LINUX_EFI_CRASH_GUID; | ||
435 | struct efivars *efivars = psi->data; | ||
436 | char name[DUMP_NAME_LEN]; | ||
437 | int i; | ||
438 | unsigned int part, size; | ||
439 | unsigned long time; | ||
440 | |||
441 | while (&efivars->walk_entry->list != &efivars->list) { | ||
442 | if (!efi_guidcmp(efivars->walk_entry->var.VendorGuid, | ||
443 | vendor)) { | ||
444 | for (i = 0; i < DUMP_NAME_LEN; i++) { | ||
445 | name[i] = efivars->walk_entry->var.VariableName[i]; | ||
446 | } | ||
447 | if (sscanf(name, "dump-type%u-%u-%lu", type, &part, &time) == 3) { | ||
448 | *id = part; | ||
449 | timespec->tv_sec = time; | ||
450 | timespec->tv_nsec = 0; | ||
451 | get_var_data_locked(efivars, &efivars->walk_entry->var); | ||
452 | size = efivars->walk_entry->var.DataSize; | ||
453 | memcpy(psi->buf, efivars->walk_entry->var.Data, size); | ||
454 | efivars->walk_entry = list_entry(efivars->walk_entry->list.next, | ||
455 | struct efivar_entry, list); | ||
456 | return size; | ||
457 | } | ||
458 | } | ||
459 | efivars->walk_entry = list_entry(efivars->walk_entry->list.next, | ||
460 | struct efivar_entry, list); | ||
461 | } | ||
462 | return 0; | ||
463 | } | ||
464 | |||
465 | static u64 efi_pstore_write(enum pstore_type_id type, unsigned int part, | ||
466 | size_t size, struct pstore_info *psi) | ||
467 | { | ||
468 | char name[DUMP_NAME_LEN]; | ||
469 | char stub_name[DUMP_NAME_LEN]; | ||
470 | efi_char16_t efi_name[DUMP_NAME_LEN]; | ||
471 | efi_guid_t vendor = LINUX_EFI_CRASH_GUID; | ||
472 | struct efivars *efivars = psi->data; | ||
473 | struct efivar_entry *entry, *found = NULL; | ||
474 | int i; | ||
475 | |||
476 | sprintf(stub_name, "dump-type%u-%u-", type, part); | ||
477 | sprintf(name, "%s%lu", stub_name, get_seconds()); | ||
478 | |||
479 | spin_lock(&efivars->lock); | ||
480 | |||
481 | for (i = 0; i < DUMP_NAME_LEN; i++) | ||
482 | efi_name[i] = stub_name[i]; | ||
483 | |||
484 | /* | ||
485 | * Clean up any entries with the same name | ||
486 | */ | ||
487 | |||
488 | list_for_each_entry(entry, &efivars->list, list) { | ||
489 | get_var_data_locked(efivars, &entry->var); | ||
490 | |||
491 | for (i = 0; i < DUMP_NAME_LEN; i++) { | ||
492 | if (efi_name[i] == 0) { | ||
493 | found = entry; | ||
494 | efivars->ops->set_variable(entry->var.VariableName, | ||
495 | &entry->var.VendorGuid, | ||
496 | EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, | ||
497 | 0, NULL); | ||
498 | break; | ||
499 | } else if (efi_name[i] != entry->var.VariableName[i]) { | ||
500 | break; | ||
501 | } | ||
502 | } | ||
503 | } | ||
504 | |||
505 | if (found) | ||
506 | list_del(&found->list); | ||
507 | |||
508 | for (i = 0; i < DUMP_NAME_LEN; i++) | ||
509 | efi_name[i] = name[i]; | ||
510 | |||
511 | efivars->ops->set_variable(efi_name, &vendor, | ||
512 | EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, | ||
513 | size, psi->buf); | ||
514 | |||
515 | spin_unlock(&efivars->lock); | ||
516 | |||
517 | if (found) | ||
518 | efivar_unregister(found); | ||
519 | |||
520 | if (size) | ||
521 | efivar_create_sysfs_entry(efivars, utf8_strsize(efi_name, DUMP_NAME_LEN * 2), | ||
522 | efi_name, &vendor); | ||
523 | |||
524 | return part; | ||
525 | }; | ||
526 | |||
527 | static int efi_pstore_erase(enum pstore_type_id type, u64 id, | ||
528 | struct pstore_info *psi) | ||
529 | { | ||
530 | efi_pstore_write(type, id, 0, psi); | ||
531 | |||
532 | return 0; | ||
533 | } | ||
534 | #else | ||
535 | static int efi_pstore_open(struct pstore_info *psi) | ||
536 | { | ||
537 | return 0; | ||
538 | } | ||
539 | |||
540 | static int efi_pstore_close(struct pstore_info *psi) | ||
541 | { | ||
542 | return 0; | ||
543 | } | ||
544 | |||
545 | static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type, | ||
546 | struct timespec *time, struct pstore_info *psi) | ||
547 | { | ||
548 | return -1; | ||
549 | } | ||
550 | |||
551 | static u64 efi_pstore_write(enum pstore_type_id type, int part, size_t size, | ||
552 | struct pstore_info *psi) | ||
553 | { | ||
554 | return 0; | ||
555 | } | ||
556 | |||
557 | static int efi_pstore_erase(enum pstore_type_id type, u64 id, | ||
558 | struct pstore_info *psi) | ||
559 | { | ||
560 | return 0; | ||
561 | } | ||
562 | #endif | ||
563 | |||
564 | static struct pstore_info efi_pstore_info = { | ||
565 | .owner = THIS_MODULE, | ||
566 | .name = "efi", | ||
567 | .open = efi_pstore_open, | ||
568 | .close = efi_pstore_close, | ||
569 | .read = efi_pstore_read, | ||
570 | .write = efi_pstore_write, | ||
571 | .erase = efi_pstore_erase, | ||
572 | }; | ||
396 | 573 | ||
397 | static ssize_t efivar_create(struct file *filp, struct kobject *kobj, | 574 | static ssize_t efivar_create(struct file *filp, struct kobject *kobj, |
398 | struct bin_attribute *bin_attr, | 575 | struct bin_attribute *bin_attr, |
@@ -763,6 +940,16 @@ int register_efivars(struct efivars *efivars, | |||
763 | if (error) | 940 | if (error) |
764 | unregister_efivars(efivars); | 941 | unregister_efivars(efivars); |
765 | 942 | ||
943 | efivars->efi_pstore_info = efi_pstore_info; | ||
944 | |||
945 | efivars->efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL); | ||
946 | if (efivars->efi_pstore_info.buf) { | ||
947 | efivars->efi_pstore_info.bufsize = 1024; | ||
948 | efivars->efi_pstore_info.data = efivars; | ||
949 | mutex_init(&efivars->efi_pstore_info.buf_mutex); | ||
950 | pstore_register(&efivars->efi_pstore_info); | ||
951 | } | ||
952 | |||
766 | out: | 953 | out: |
767 | kfree(variable_name); | 954 | kfree(variable_name); |
768 | 955 | ||