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 | ||