diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2011-08-01 19:40:51 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-08-01 19:40:51 -0400 |
| commit | a2d773023552f68baa2db2226dfd6d761c0df5da (patch) | |
| tree | c905702c4f318d0e569222187472289c574bde3c /drivers/firmware | |
| parent | 72f9adfd20e3be8a33ff3ef96cec787ed97b9ba9 (diff) | |
| parent | 7644c16c7e7431fa398e834109dbb76dc1b51617 (diff) | |
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
Diffstat (limited to 'drivers/firmware')
| -rw-r--r-- | drivers/firmware/efivars.c | 243 |
1 files changed, 231 insertions, 12 deletions
diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c index 5f29aafd4462..eacb05e6cfb3 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 |
| @@ -119,6 +122,10 @@ struct efivar_attribute { | |||
| 119 | ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count); | 122 | ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count); |
| 120 | }; | 123 | }; |
| 121 | 124 | ||
| 125 | #define PSTORE_EFI_ATTRIBUTES \ | ||
| 126 | (EFI_VARIABLE_NON_VOLATILE | \ | ||
| 127 | EFI_VARIABLE_BOOTSERVICE_ACCESS | \ | ||
| 128 | EFI_VARIABLE_RUNTIME_ACCESS) | ||
| 122 | 129 | ||
| 123 | #define EFIVAR_ATTR(_name, _mode, _show, _store) \ | 130 | #define EFIVAR_ATTR(_name, _mode, _show, _store) \ |
| 124 | struct efivar_attribute efivar_attr_##_name = { \ | 131 | struct efivar_attribute efivar_attr_##_name = { \ |
| @@ -141,38 +148,72 @@ efivar_create_sysfs_entry(struct efivars *efivars, | |||
| 141 | 148 | ||
| 142 | /* Return the number of unicode characters in data */ | 149 | /* Return the number of unicode characters in data */ |
| 143 | static unsigned long | 150 | static unsigned long |
| 144 | utf8_strlen(efi_char16_t *data, unsigned long maxlength) | 151 | utf16_strnlen(efi_char16_t *s, size_t maxlength) |
| 145 | { | 152 | { |
| 146 | unsigned long length = 0; | 153 | unsigned long length = 0; |
| 147 | 154 | ||
| 148 | while (*data++ != 0 && length < maxlength) | 155 | while (*s++ != 0 && length < maxlength) |
| 149 | length++; | 156 | length++; |
| 150 | return length; | 157 | return length; |
| 151 | } | 158 | } |
| 152 | 159 | ||
| 160 | static unsigned long | ||
| 161 | utf16_strlen(efi_char16_t *s) | ||
| 162 | { | ||
| 163 | return utf16_strnlen(s, ~0UL); | ||
| 164 | } | ||
| 165 | |||
| 153 | /* | 166 | /* |
| 154 | * Return the number of bytes is the length of this string | 167 | * Return the number of bytes is the length of this string |
| 155 | * Note: this is NOT the same as the number of unicode characters | 168 | * Note: this is NOT the same as the number of unicode characters |
| 156 | */ | 169 | */ |
| 157 | static inline unsigned long | 170 | static inline unsigned long |
| 158 | utf8_strsize(efi_char16_t *data, unsigned long maxlength) | 171 | utf16_strsize(efi_char16_t *data, unsigned long maxlength) |
| 159 | { | 172 | { |
| 160 | return utf8_strlen(data, maxlength/sizeof(efi_char16_t)) * sizeof(efi_char16_t); | 173 | return utf16_strnlen(data, maxlength/sizeof(efi_char16_t)) * sizeof(efi_char16_t); |
| 174 | } | ||
| 175 | |||
| 176 | static inline int | ||
| 177 | utf16_strncmp(const efi_char16_t *a, const efi_char16_t *b, size_t len) | ||
| 178 | { | ||
| 179 | while (1) { | ||
| 180 | if (len == 0) | ||
| 181 | return 0; | ||
| 182 | if (*a < *b) | ||
| 183 | return -1; | ||
| 184 | if (*a > *b) | ||
| 185 | return 1; | ||
| 186 | if (*a == 0) /* implies *b == 0 */ | ||
| 187 | return 0; | ||
| 188 | a++; | ||
| 189 | b++; | ||
| 190 | len--; | ||
| 191 | } | ||
| 161 | } | 192 | } |
| 162 | 193 | ||
| 163 | static efi_status_t | 194 | static efi_status_t |
| 164 | get_var_data(struct efivars *efivars, struct efi_variable *var) | 195 | get_var_data_locked(struct efivars *efivars, struct efi_variable *var) |
| 165 | { | 196 | { |
| 166 | efi_status_t status; | 197 | efi_status_t status; |
| 167 | 198 | ||
| 168 | spin_lock(&efivars->lock); | ||
| 169 | var->DataSize = 1024; | 199 | var->DataSize = 1024; |
| 170 | status = efivars->ops->get_variable(var->VariableName, | 200 | status = efivars->ops->get_variable(var->VariableName, |
| 171 | &var->VendorGuid, | 201 | &var->VendorGuid, |
| 172 | &var->Attributes, | 202 | &var->Attributes, |
| 173 | &var->DataSize, | 203 | &var->DataSize, |
| 174 | var->Data); | 204 | var->Data); |
| 205 | return status; | ||
| 206 | } | ||
| 207 | |||
| 208 | static efi_status_t | ||
| 209 | get_var_data(struct efivars *efivars, struct efi_variable *var) | ||
| 210 | { | ||
| 211 | efi_status_t status; | ||
| 212 | |||
| 213 | spin_lock(&efivars->lock); | ||
| 214 | status = get_var_data_locked(efivars, var); | ||
| 175 | spin_unlock(&efivars->lock); | 215 | spin_unlock(&efivars->lock); |
| 216 | |||
| 176 | if (status != EFI_SUCCESS) { | 217 | if (status != EFI_SUCCESS) { |
| 177 | printk(KERN_WARNING "efivars: get_variable() failed 0x%lx!\n", | 218 | printk(KERN_WARNING "efivars: get_variable() failed 0x%lx!\n", |
| 178 | status); | 219 | status); |
| @@ -387,12 +428,180 @@ static struct kobj_type efivar_ktype = { | |||
| 387 | .default_attrs = def_attrs, | 428 | .default_attrs = def_attrs, |
| 388 | }; | 429 | }; |
| 389 | 430 | ||
| 431 | static struct pstore_info efi_pstore_info; | ||
| 432 | |||
| 390 | static inline void | 433 | static inline void |
| 391 | efivar_unregister(struct efivar_entry *var) | 434 | efivar_unregister(struct efivar_entry *var) |
| 392 | { | 435 | { |
| 393 | kobject_put(&var->kobj); | 436 | kobject_put(&var->kobj); |
| 394 | } | 437 | } |
| 395 | 438 | ||
| 439 | #ifdef CONFIG_PSTORE | ||
| 440 | |||
| 441 | static int efi_pstore_open(struct pstore_info *psi) | ||
| 442 | { | ||
| 443 | struct efivars *efivars = psi->data; | ||
| 444 | |||
| 445 | spin_lock(&efivars->lock); | ||
| 446 | efivars->walk_entry = list_first_entry(&efivars->list, | ||
| 447 | struct efivar_entry, list); | ||
| 448 | return 0; | ||
| 449 | } | ||
| 450 | |||
| 451 | static int efi_pstore_close(struct pstore_info *psi) | ||
| 452 | { | ||
| 453 | struct efivars *efivars = psi->data; | ||
| 454 | |||
| 455 | spin_unlock(&efivars->lock); | ||
| 456 | return 0; | ||
| 457 | } | ||
| 458 | |||
| 459 | static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type, | ||
| 460 | struct timespec *timespec, struct pstore_info *psi) | ||
| 461 | { | ||
| 462 | efi_guid_t vendor = LINUX_EFI_CRASH_GUID; | ||
| 463 | struct efivars *efivars = psi->data; | ||
| 464 | char name[DUMP_NAME_LEN]; | ||
| 465 | int i; | ||
| 466 | unsigned int part, size; | ||
| 467 | unsigned long time; | ||
| 468 | |||
| 469 | while (&efivars->walk_entry->list != &efivars->list) { | ||
| 470 | if (!efi_guidcmp(efivars->walk_entry->var.VendorGuid, | ||
| 471 | vendor)) { | ||
| 472 | for (i = 0; i < DUMP_NAME_LEN; i++) { | ||
| 473 | name[i] = efivars->walk_entry->var.VariableName[i]; | ||
| 474 | } | ||
| 475 | if (sscanf(name, "dump-type%u-%u-%lu", type, &part, &time) == 3) { | ||
| 476 | *id = part; | ||
| 477 | timespec->tv_sec = time; | ||
| 478 | timespec->tv_nsec = 0; | ||
| 479 | get_var_data_locked(efivars, &efivars->walk_entry->var); | ||
| 480 | size = efivars->walk_entry->var.DataSize; | ||
| 481 | memcpy(psi->buf, efivars->walk_entry->var.Data, size); | ||
| 482 | efivars->walk_entry = list_entry(efivars->walk_entry->list.next, | ||
| 483 | struct efivar_entry, list); | ||
| 484 | return size; | ||
| 485 | } | ||
| 486 | } | ||
| 487 | efivars->walk_entry = list_entry(efivars->walk_entry->list.next, | ||
| 488 | struct efivar_entry, list); | ||
| 489 | } | ||
| 490 | return 0; | ||
| 491 | } | ||
| 492 | |||
| 493 | static u64 efi_pstore_write(enum pstore_type_id type, unsigned int part, | ||
| 494 | size_t size, struct pstore_info *psi) | ||
| 495 | { | ||
| 496 | char name[DUMP_NAME_LEN]; | ||
| 497 | char stub_name[DUMP_NAME_LEN]; | ||
| 498 | efi_char16_t efi_name[DUMP_NAME_LEN]; | ||
| 499 | efi_guid_t vendor = LINUX_EFI_CRASH_GUID; | ||
| 500 | struct efivars *efivars = psi->data; | ||
| 501 | struct efivar_entry *entry, *found = NULL; | ||
| 502 | int i; | ||
| 503 | |||
| 504 | sprintf(stub_name, "dump-type%u-%u-", type, part); | ||
| 505 | sprintf(name, "%s%lu", stub_name, get_seconds()); | ||
| 506 | |||
| 507 | spin_lock(&efivars->lock); | ||
| 508 | |||
| 509 | for (i = 0; i < DUMP_NAME_LEN; i++) | ||
| 510 | efi_name[i] = stub_name[i]; | ||
| 511 | |||
| 512 | /* | ||
| 513 | * Clean up any entries with the same name | ||
| 514 | */ | ||
| 515 | |||
| 516 | list_for_each_entry(entry, &efivars->list, list) { | ||
| 517 | get_var_data_locked(efivars, &entry->var); | ||
| 518 | |||
| 519 | if (efi_guidcmp(entry->var.VendorGuid, vendor)) | ||
| 520 | continue; | ||
| 521 | if (utf16_strncmp(entry->var.VariableName, efi_name, | ||
| 522 | utf16_strlen(efi_name))) | ||
| 523 | continue; | ||
| 524 | /* Needs to be a prefix */ | ||
| 525 | if (entry->var.VariableName[utf16_strlen(efi_name)] == 0) | ||
| 526 | continue; | ||
| 527 | |||
| 528 | /* found */ | ||
| 529 | found = entry; | ||
| 530 | efivars->ops->set_variable(entry->var.VariableName, | ||
| 531 | &entry->var.VendorGuid, | ||
| 532 | PSTORE_EFI_ATTRIBUTES, | ||
| 533 | 0, NULL); | ||
| 534 | } | ||
| 535 | |||
| 536 | if (found) | ||
| 537 | list_del(&found->list); | ||
| 538 | |||
| 539 | for (i = 0; i < DUMP_NAME_LEN; i++) | ||
| 540 | efi_name[i] = name[i]; | ||
| 541 | |||
| 542 | efivars->ops->set_variable(efi_name, &vendor, PSTORE_EFI_ATTRIBUTES, | ||
| 543 | size, psi->buf); | ||
| 544 | |||
| 545 | spin_unlock(&efivars->lock); | ||
| 546 | |||
| 547 | if (found) | ||
| 548 | efivar_unregister(found); | ||
| 549 | |||
| 550 | if (size) | ||
| 551 | efivar_create_sysfs_entry(efivars, | ||
| 552 | utf16_strsize(efi_name, | ||
| 553 | DUMP_NAME_LEN * 2), | ||
| 554 | efi_name, &vendor); | ||
| 555 | |||
| 556 | return part; | ||
| 557 | }; | ||
| 558 | |||
| 559 | static int efi_pstore_erase(enum pstore_type_id type, u64 id, | ||
| 560 | struct pstore_info *psi) | ||
| 561 | { | ||
| 562 | efi_pstore_write(type, id, 0, psi); | ||
| 563 | |||
| 564 | return 0; | ||
| 565 | } | ||
| 566 | #else | ||
| 567 | static int efi_pstore_open(struct pstore_info *psi) | ||
| 568 | { | ||
| 569 | return 0; | ||
| 570 | } | ||
| 571 | |||
| 572 | static int efi_pstore_close(struct pstore_info *psi) | ||
| 573 | { | ||
| 574 | return 0; | ||
| 575 | } | ||
| 576 | |||
| 577 | static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type, | ||
| 578 | struct timespec *time, struct pstore_info *psi) | ||
| 579 | { | ||
| 580 | return -1; | ||
| 581 | } | ||
| 582 | |||
| 583 | static u64 efi_pstore_write(enum pstore_type_id type, int part, size_t size, | ||
| 584 | struct pstore_info *psi) | ||
| 585 | { | ||
| 586 | return 0; | ||
| 587 | } | ||
| 588 | |||
| 589 | static int efi_pstore_erase(enum pstore_type_id type, u64 id, | ||
| 590 | struct pstore_info *psi) | ||
| 591 | { | ||
| 592 | return 0; | ||
| 593 | } | ||
| 594 | #endif | ||
| 595 | |||
| 596 | static struct pstore_info efi_pstore_info = { | ||
| 597 | .owner = THIS_MODULE, | ||
| 598 | .name = "efi", | ||
| 599 | .open = efi_pstore_open, | ||
| 600 | .close = efi_pstore_close, | ||
| 601 | .read = efi_pstore_read, | ||
| 602 | .write = efi_pstore_write, | ||
| 603 | .erase = efi_pstore_erase, | ||
| 604 | }; | ||
| 396 | 605 | ||
| 397 | static ssize_t efivar_create(struct file *filp, struct kobject *kobj, | 606 | static ssize_t efivar_create(struct file *filp, struct kobject *kobj, |
| 398 | struct bin_attribute *bin_attr, | 607 | struct bin_attribute *bin_attr, |
| @@ -414,8 +623,8 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj, | |||
| 414 | * Does this variable already exist? | 623 | * Does this variable already exist? |
| 415 | */ | 624 | */ |
| 416 | list_for_each_entry_safe(search_efivar, n, &efivars->list, list) { | 625 | list_for_each_entry_safe(search_efivar, n, &efivars->list, list) { |
| 417 | strsize1 = utf8_strsize(search_efivar->var.VariableName, 1024); | 626 | strsize1 = utf16_strsize(search_efivar->var.VariableName, 1024); |
| 418 | strsize2 = utf8_strsize(new_var->VariableName, 1024); | 627 | strsize2 = utf16_strsize(new_var->VariableName, 1024); |
| 419 | if (strsize1 == strsize2 && | 628 | if (strsize1 == strsize2 && |
| 420 | !memcmp(&(search_efivar->var.VariableName), | 629 | !memcmp(&(search_efivar->var.VariableName), |
| 421 | new_var->VariableName, strsize1) && | 630 | new_var->VariableName, strsize1) && |
| @@ -447,8 +656,8 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj, | |||
| 447 | 656 | ||
| 448 | /* Create the entry in sysfs. Locking is not required here */ | 657 | /* Create the entry in sysfs. Locking is not required here */ |
| 449 | status = efivar_create_sysfs_entry(efivars, | 658 | status = efivar_create_sysfs_entry(efivars, |
| 450 | utf8_strsize(new_var->VariableName, | 659 | utf16_strsize(new_var->VariableName, |
| 451 | 1024), | 660 | 1024), |
| 452 | new_var->VariableName, | 661 | new_var->VariableName, |
| 453 | &new_var->VendorGuid); | 662 | &new_var->VendorGuid); |
| 454 | if (status) { | 663 | if (status) { |
| @@ -477,8 +686,8 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj, | |||
| 477 | * Does this variable already exist? | 686 | * Does this variable already exist? |
| 478 | */ | 687 | */ |
| 479 | list_for_each_entry_safe(search_efivar, n, &efivars->list, list) { | 688 | list_for_each_entry_safe(search_efivar, n, &efivars->list, list) { |
| 480 | strsize1 = utf8_strsize(search_efivar->var.VariableName, 1024); | 689 | strsize1 = utf16_strsize(search_efivar->var.VariableName, 1024); |
| 481 | strsize2 = utf8_strsize(del_var->VariableName, 1024); | 690 | strsize2 = utf16_strsize(del_var->VariableName, 1024); |
| 482 | if (strsize1 == strsize2 && | 691 | if (strsize1 == strsize2 && |
| 483 | !memcmp(&(search_efivar->var.VariableName), | 692 | !memcmp(&(search_efivar->var.VariableName), |
| 484 | del_var->VariableName, strsize1) && | 693 | del_var->VariableName, strsize1) && |
| @@ -763,6 +972,16 @@ int register_efivars(struct efivars *efivars, | |||
| 763 | if (error) | 972 | if (error) |
| 764 | unregister_efivars(efivars); | 973 | unregister_efivars(efivars); |
| 765 | 974 | ||
| 975 | efivars->efi_pstore_info = efi_pstore_info; | ||
| 976 | |||
| 977 | efivars->efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL); | ||
| 978 | if (efivars->efi_pstore_info.buf) { | ||
| 979 | efivars->efi_pstore_info.bufsize = 1024; | ||
| 980 | efivars->efi_pstore_info.data = efivars; | ||
| 981 | mutex_init(&efivars->efi_pstore_info.buf_mutex); | ||
| 982 | pstore_register(&efivars->efi_pstore_info); | ||
| 983 | } | ||
| 984 | |||
| 766 | out: | 985 | out: |
| 767 | kfree(variable_name); | 986 | kfree(variable_name); |
| 768 | 987 | ||
