diff options
| -rw-r--r-- | Documentation/acpi/apei/einj.txt | 55 | ||||
| -rw-r--r-- | drivers/acpi/apei/einj.c | 224 | ||||
| -rw-r--r-- | include/acpi/actbl1.h | 3 |
3 files changed, 234 insertions, 48 deletions
diff --git a/Documentation/acpi/apei/einj.txt b/Documentation/acpi/apei/einj.txt index 5cc699ba5453..e7cc36397217 100644 --- a/Documentation/acpi/apei/einj.txt +++ b/Documentation/acpi/apei/einj.txt | |||
| @@ -47,20 +47,53 @@ directory apei/einj. The following files are provided. | |||
| 47 | 47 | ||
| 48 | - param1 | 48 | - param1 |
| 49 | This file is used to set the first error parameter value. Effect of | 49 | This file is used to set the first error parameter value. Effect of |
| 50 | parameter depends on error_type specified. For memory error, this is | 50 | parameter depends on error_type specified. |
| 51 | physical memory address. Only available if param_extension module | ||
| 52 | parameter is specified. | ||
| 53 | 51 | ||
| 54 | - param2 | 52 | - param2 |
| 55 | This file is used to set the second error parameter value. Effect of | 53 | This file is used to set the second error parameter value. Effect of |
| 56 | parameter depends on error_type specified. For memory error, this is | 54 | parameter depends on error_type specified. |
| 57 | physical memory address mask. Only available if param_extension | 55 | |
| 58 | module parameter is specified. | 56 | BIOS versions based in the ACPI 4.0 specification have limited options |
| 57 | to control where the errors are injected. Your BIOS may support an | ||
| 58 | extension (enabled with the param_extension=1 module parameter, or | ||
| 59 | boot command line einj.param_extension=1). This allows the address | ||
| 60 | and mask for memory injections to be specified by the param1 and | ||
| 61 | param2 files in apei/einj. | ||
| 62 | |||
| 63 | BIOS versions using the ACPI 5.0 specification have more control over | ||
| 64 | the target of the injection. For processor related errors (type 0x1, | ||
| 65 | 0x2 and 0x4) the APICID of the target should be provided using the | ||
| 66 | param1 file in apei/einj. For memory errors (type 0x8, 0x10 and 0x20) | ||
| 67 | the address is set using param1 with a mask in param2 (0x0 is equivalent | ||
| 68 | to all ones). For PCI express errors (type 0x40, 0x80 and 0x100) the | ||
| 69 | segment, bus, device and function are specified using param1: | ||
| 70 | |||
| 71 | 31 24 23 16 15 11 10 8 7 0 | ||
| 72 | +-------------------------------------------------+ | ||
| 73 | | segment | bus | device | function | reserved | | ||
| 74 | +-------------------------------------------------+ | ||
| 75 | |||
| 76 | An ACPI 5.0 BIOS may also allow vendor specific errors to be injected. | ||
| 77 | In this case a file named vendor will contain identifying information | ||
| 78 | from the BIOS that hopefully will allow an application wishing to use | ||
| 79 | the vendor specific extension to tell that they are running on a BIOS | ||
| 80 | that supports it. All vendor extensions have the 0x80000000 bit set in | ||
| 81 | error_type. A file vendor_flags controls the interpretation of param1 | ||
| 82 | and param2 (1 = PROCESSOR, 2 = MEMORY, 4 = PCI). See your BIOS vendor | ||
| 83 | documentation for details (and expect changes to this API if vendors | ||
| 84 | creativity in using this feature expands beyond our expectations). | ||
| 85 | |||
| 86 | Example: | ||
| 87 | # cd /sys/kernel/debug/apei/einj | ||
| 88 | # cat available_error_type # See which errors can be injected | ||
| 89 | 0x00000002 Processor Uncorrectable non-fatal | ||
| 90 | 0x00000008 Memory Correctable | ||
| 91 | 0x00000010 Memory Uncorrectable non-fatal | ||
| 92 | # echo 0x12345000 > param1 # Set memory address for injection | ||
| 93 | # echo 0xfffffffffffff000 > param2 # Mask - anywhere in this page | ||
| 94 | # echo 0x8 > error_type # Choose correctable memory error | ||
| 95 | # echo 1 > error_inject # Inject now | ||
| 59 | 96 | ||
| 60 | Injecting parameter support is a BIOS version specific extension, that | ||
| 61 | is, it only works on some BIOS version. If you want to use it, please | ||
| 62 | make sure your BIOS version has the proper support and specify | ||
| 63 | "param_extension=y" in module parameter. | ||
| 64 | 97 | ||
| 65 | For more information about EINJ, please refer to ACPI specification | 98 | For more information about EINJ, please refer to ACPI specification |
| 66 | version 4.0, section 17.5. | 99 | version 4.0, section 17.5 and ACPI 5.0, section 18.6. |
diff --git a/drivers/acpi/apei/einj.c b/drivers/acpi/apei/einj.c index 589b96c38704..31546fd21029 100644 --- a/drivers/acpi/apei/einj.c +++ b/drivers/acpi/apei/einj.c | |||
| @@ -43,6 +43,42 @@ | |||
| 43 | #define FIRMWARE_TIMEOUT (1 * NSEC_PER_MSEC) | 43 | #define FIRMWARE_TIMEOUT (1 * NSEC_PER_MSEC) |
| 44 | 44 | ||
| 45 | /* | 45 | /* |
| 46 | * ACPI version 5 provides a SET_ERROR_TYPE_WITH_ADDRESS action. | ||
| 47 | */ | ||
| 48 | static int acpi5; | ||
| 49 | |||
| 50 | struct set_error_type_with_address { | ||
| 51 | u32 type; | ||
| 52 | u32 vendor_extension; | ||
| 53 | u32 flags; | ||
| 54 | u32 apicid; | ||
| 55 | u64 memory_address; | ||
| 56 | u64 memory_address_range; | ||
| 57 | u32 pcie_sbdf; | ||
| 58 | }; | ||
| 59 | enum { | ||
| 60 | SETWA_FLAGS_APICID = 1, | ||
| 61 | SETWA_FLAGS_MEM = 2, | ||
| 62 | SETWA_FLAGS_PCIE_SBDF = 4, | ||
| 63 | }; | ||
| 64 | |||
| 65 | /* | ||
| 66 | * Vendor extensions for platform specific operations | ||
| 67 | */ | ||
| 68 | struct vendor_error_type_extension { | ||
| 69 | u32 length; | ||
| 70 | u32 pcie_sbdf; | ||
| 71 | u16 vendor_id; | ||
| 72 | u16 device_id; | ||
| 73 | u8 rev_id; | ||
| 74 | u8 reserved[3]; | ||
| 75 | }; | ||
| 76 | |||
| 77 | static u32 vendor_flags; | ||
| 78 | static struct debugfs_blob_wrapper vendor_blob; | ||
| 79 | static char vendor_dev[64]; | ||
| 80 | |||
| 81 | /* | ||
| 46 | * Some BIOSes allow parameters to the SET_ERROR_TYPE entries in the | 82 | * Some BIOSes allow parameters to the SET_ERROR_TYPE entries in the |
| 47 | * EINJ table through an unpublished extension. Use with caution as | 83 | * EINJ table through an unpublished extension. Use with caution as |
| 48 | * most will ignore the parameter and make their own choice of address | 84 | * most will ignore the parameter and make their own choice of address |
| @@ -103,7 +139,14 @@ static struct apei_exec_ins_type einj_ins_type[] = { | |||
| 103 | */ | 139 | */ |
| 104 | static DEFINE_MUTEX(einj_mutex); | 140 | static DEFINE_MUTEX(einj_mutex); |
| 105 | 141 | ||
| 106 | static struct einj_parameter *einj_param; | 142 | static void *einj_param; |
| 143 | |||
| 144 | #ifndef readq | ||
| 145 | static inline __u64 readq(volatile void __iomem *addr) | ||
| 146 | { | ||
| 147 | return ((__u64)readl(addr+4) << 32) + readl(addr); | ||
| 148 | } | ||
| 149 | #endif | ||
| 107 | 150 | ||
| 108 | #ifndef writeq | 151 | #ifndef writeq |
| 109 | static inline void writeq(__u64 val, volatile void __iomem *addr) | 152 | static inline void writeq(__u64 val, volatile void __iomem *addr) |
| @@ -158,10 +201,31 @@ static int einj_timedout(u64 *t) | |||
| 158 | return 0; | 201 | return 0; |
| 159 | } | 202 | } |
| 160 | 203 | ||
| 161 | static u64 einj_get_parameter_address(void) | 204 | static void check_vendor_extension(u64 paddr, |
| 205 | struct set_error_type_with_address *v5param) | ||
| 206 | { | ||
| 207 | int offset = readl(&v5param->vendor_extension); | ||
| 208 | struct vendor_error_type_extension *v; | ||
| 209 | u32 sbdf; | ||
| 210 | |||
| 211 | if (!offset) | ||
| 212 | return; | ||
| 213 | v = ioremap(paddr + offset, sizeof(*v)); | ||
| 214 | if (!v) | ||
| 215 | return; | ||
| 216 | sbdf = readl(&v->pcie_sbdf); | ||
| 217 | sprintf(vendor_dev, "%x:%x:%x.%x vendor_id=%x device_id=%x rev_id=%x\n", | ||
| 218 | sbdf >> 24, (sbdf >> 16) & 0xff, | ||
| 219 | (sbdf >> 11) & 0x1f, (sbdf >> 8) & 0x7, | ||
| 220 | readw(&v->vendor_id), readw(&v->device_id), | ||
| 221 | readb(&v->rev_id)); | ||
| 222 | iounmap(v); | ||
| 223 | } | ||
| 224 | |||
| 225 | static void *einj_get_parameter_address(void) | ||
| 162 | { | 226 | { |
| 163 | int i; | 227 | int i; |
| 164 | u64 paddr = 0; | 228 | u64 paddrv4 = 0, paddrv5 = 0; |
| 165 | struct acpi_whea_header *entry; | 229 | struct acpi_whea_header *entry; |
| 166 | 230 | ||
| 167 | entry = EINJ_TAB_ENTRY(einj_tab); | 231 | entry = EINJ_TAB_ENTRY(einj_tab); |
| @@ -170,12 +234,40 @@ static u64 einj_get_parameter_address(void) | |||
| 170 | entry->instruction == ACPI_EINJ_WRITE_REGISTER && | 234 | entry->instruction == ACPI_EINJ_WRITE_REGISTER && |
| 171 | entry->register_region.space_id == | 235 | entry->register_region.space_id == |
| 172 | ACPI_ADR_SPACE_SYSTEM_MEMORY) | 236 | ACPI_ADR_SPACE_SYSTEM_MEMORY) |
| 173 | memcpy(&paddr, &entry->register_region.address, | 237 | memcpy(&paddrv4, &entry->register_region.address, |
| 174 | sizeof(paddr)); | 238 | sizeof(paddrv4)); |
| 239 | if (entry->action == ACPI_EINJ_SET_ERROR_TYPE_WITH_ADDRESS && | ||
| 240 | entry->instruction == ACPI_EINJ_WRITE_REGISTER && | ||
| 241 | entry->register_region.space_id == | ||
| 242 | ACPI_ADR_SPACE_SYSTEM_MEMORY) | ||
| 243 | memcpy(&paddrv5, &entry->register_region.address, | ||
| 244 | sizeof(paddrv5)); | ||
| 175 | entry++; | 245 | entry++; |
| 176 | } | 246 | } |
| 247 | if (paddrv5) { | ||
| 248 | struct set_error_type_with_address *v5param; | ||
| 249 | |||
| 250 | v5param = ioremap(paddrv5, sizeof(*v5param)); | ||
| 251 | if (v5param) { | ||
| 252 | acpi5 = 1; | ||
| 253 | check_vendor_extension(paddrv5, v5param); | ||
| 254 | return v5param; | ||
| 255 | } | ||
| 256 | } | ||
| 257 | if (paddrv4) { | ||
| 258 | struct einj_parameter *v4param; | ||
| 259 | |||
| 260 | v4param = ioremap(paddrv4, sizeof(*v4param)); | ||
| 261 | if (!v4param) | ||
| 262 | return 0; | ||
| 263 | if (readq(&v4param->reserved1) || readq(&v4param->reserved2)) { | ||
| 264 | iounmap(v4param); | ||
| 265 | return 0; | ||
| 266 | } | ||
| 267 | return v4param; | ||
| 268 | } | ||
| 177 | 269 | ||
| 178 | return paddr; | 270 | return 0; |
| 179 | } | 271 | } |
| 180 | 272 | ||
| 181 | /* do sanity check to trigger table */ | 273 | /* do sanity check to trigger table */ |
| @@ -293,12 +385,56 @@ static int __einj_error_inject(u32 type, u64 param1, u64 param2) | |||
| 293 | if (rc) | 385 | if (rc) |
| 294 | return rc; | 386 | return rc; |
| 295 | apei_exec_ctx_set_input(&ctx, type); | 387 | apei_exec_ctx_set_input(&ctx, type); |
| 296 | rc = apei_exec_run(&ctx, ACPI_EINJ_SET_ERROR_TYPE); | 388 | if (acpi5) { |
| 297 | if (rc) | 389 | struct set_error_type_with_address *v5param = einj_param; |
| 298 | return rc; | 390 | |
| 299 | if (einj_param) { | 391 | writel(type, &v5param->type); |
| 300 | writeq(param1, &einj_param->param1); | 392 | if (type & 0x80000000) { |
| 301 | writeq(param2, &einj_param->param2); | 393 | switch (vendor_flags) { |
| 394 | case SETWA_FLAGS_APICID: | ||
| 395 | writel(param1, &v5param->apicid); | ||
| 396 | break; | ||
| 397 | case SETWA_FLAGS_MEM: | ||
| 398 | writeq(param1, &v5param->memory_address); | ||
| 399 | writeq(param2, &v5param->memory_address_range); | ||
| 400 | break; | ||
| 401 | case SETWA_FLAGS_PCIE_SBDF: | ||
| 402 | writel(param1, &v5param->pcie_sbdf); | ||
| 403 | break; | ||
| 404 | } | ||
| 405 | writel(vendor_flags, &v5param->flags); | ||
| 406 | } else { | ||
| 407 | switch (type) { | ||
| 408 | case ACPI_EINJ_PROCESSOR_CORRECTABLE: | ||
| 409 | case ACPI_EINJ_PROCESSOR_UNCORRECTABLE: | ||
| 410 | case ACPI_EINJ_PROCESSOR_FATAL: | ||
| 411 | writel(param1, &v5param->apicid); | ||
| 412 | writel(SETWA_FLAGS_APICID, &v5param->flags); | ||
| 413 | break; | ||
| 414 | case ACPI_EINJ_MEMORY_CORRECTABLE: | ||
| 415 | case ACPI_EINJ_MEMORY_UNCORRECTABLE: | ||
| 416 | case ACPI_EINJ_MEMORY_FATAL: | ||
| 417 | writeq(param1, &v5param->memory_address); | ||
| 418 | writeq(param2, &v5param->memory_address_range); | ||
| 419 | writel(SETWA_FLAGS_MEM, &v5param->flags); | ||
| 420 | break; | ||
| 421 | case ACPI_EINJ_PCIX_CORRECTABLE: | ||
| 422 | case ACPI_EINJ_PCIX_UNCORRECTABLE: | ||
| 423 | case ACPI_EINJ_PCIX_FATAL: | ||
| 424 | writel(param1, &v5param->pcie_sbdf); | ||
| 425 | writel(SETWA_FLAGS_PCIE_SBDF, &v5param->flags); | ||
| 426 | break; | ||
| 427 | } | ||
| 428 | } | ||
| 429 | } else { | ||
| 430 | rc = apei_exec_run(&ctx, ACPI_EINJ_SET_ERROR_TYPE); | ||
| 431 | if (rc) | ||
| 432 | return rc; | ||
| 433 | if (einj_param) { | ||
| 434 | struct einj_parameter *v4param = einj_param; | ||
| 435 | writeq(param1, &v4param->param1); | ||
| 436 | writeq(param2, &v4param->param2); | ||
| 437 | } | ||
| 302 | } | 438 | } |
| 303 | rc = apei_exec_run(&ctx, ACPI_EINJ_EXECUTE_OPERATION); | 439 | rc = apei_exec_run(&ctx, ACPI_EINJ_EXECUTE_OPERATION); |
| 304 | if (rc) | 440 | if (rc) |
| @@ -408,15 +544,25 @@ static int error_type_set(void *data, u64 val) | |||
| 408 | { | 544 | { |
| 409 | int rc; | 545 | int rc; |
| 410 | u32 available_error_type = 0; | 546 | u32 available_error_type = 0; |
| 547 | u32 tval, vendor; | ||
| 548 | |||
| 549 | /* | ||
| 550 | * Vendor defined types have 0x80000000 bit set, and | ||
| 551 | * are not enumerated by ACPI_EINJ_GET_ERROR_TYPE | ||
| 552 | */ | ||
| 553 | vendor = val & 0x80000000; | ||
| 554 | tval = val & 0x7fffffff; | ||
| 411 | 555 | ||
| 412 | /* Only one error type can be specified */ | 556 | /* Only one error type can be specified */ |
| 413 | if (val & (val - 1)) | 557 | if (tval & (tval - 1)) |
| 414 | return -EINVAL; | ||
| 415 | rc = einj_get_available_error_type(&available_error_type); | ||
| 416 | if (rc) | ||
| 417 | return rc; | ||
| 418 | if (!(val & available_error_type)) | ||
| 419 | return -EINVAL; | 558 | return -EINVAL; |
| 559 | if (!vendor) { | ||
| 560 | rc = einj_get_available_error_type(&available_error_type); | ||
| 561 | if (rc) | ||
| 562 | return rc; | ||
| 563 | if (!(val & available_error_type)) | ||
| 564 | return -EINVAL; | ||
| 565 | } | ||
| 420 | error_type = val; | 566 | error_type = val; |
| 421 | 567 | ||
| 422 | return 0; | 568 | return 0; |
| @@ -455,7 +601,6 @@ static int einj_check_table(struct acpi_table_einj *einj_tab) | |||
| 455 | static int __init einj_init(void) | 601 | static int __init einj_init(void) |
| 456 | { | 602 | { |
| 457 | int rc; | 603 | int rc; |
| 458 | u64 param_paddr; | ||
| 459 | acpi_status status; | 604 | acpi_status status; |
| 460 | struct dentry *fentry; | 605 | struct dentry *fentry; |
| 461 | struct apei_exec_context ctx; | 606 | struct apei_exec_context ctx; |
| @@ -509,23 +654,30 @@ static int __init einj_init(void) | |||
| 509 | rc = apei_exec_pre_map_gars(&ctx); | 654 | rc = apei_exec_pre_map_gars(&ctx); |
| 510 | if (rc) | 655 | if (rc) |
| 511 | goto err_release; | 656 | goto err_release; |
| 512 | if (param_extension) { | 657 | |
| 513 | param_paddr = einj_get_parameter_address(); | 658 | einj_param = einj_get_parameter_address(); |
| 514 | if (param_paddr) { | 659 | if ((param_extension || acpi5) && einj_param) { |
| 515 | einj_param = ioremap(param_paddr, sizeof(*einj_param)); | 660 | fentry = debugfs_create_x64("param1", S_IRUSR | S_IWUSR, |
| 516 | rc = -ENOMEM; | 661 | einj_debug_dir, &error_param1); |
| 517 | if (!einj_param) | 662 | if (!fentry) |
| 518 | goto err_unmap; | 663 | goto err_unmap; |
| 519 | fentry = debugfs_create_x64("param1", S_IRUSR | S_IWUSR, | 664 | fentry = debugfs_create_x64("param2", S_IRUSR | S_IWUSR, |
| 520 | einj_debug_dir, &error_param1); | 665 | einj_debug_dir, &error_param2); |
| 521 | if (!fentry) | 666 | if (!fentry) |
| 522 | goto err_unmap; | 667 | goto err_unmap; |
| 523 | fentry = debugfs_create_x64("param2", S_IRUSR | S_IWUSR, | 668 | } |
| 524 | einj_debug_dir, &error_param2); | 669 | |
| 525 | if (!fentry) | 670 | if (vendor_dev[0]) { |
| 526 | goto err_unmap; | 671 | vendor_blob.data = vendor_dev; |
| 527 | } else | 672 | vendor_blob.size = strlen(vendor_dev); |
| 528 | pr_warn(EINJ_PFX "Parameter extension is not supported.\n"); | 673 | fentry = debugfs_create_blob("vendor", S_IRUSR, |
| 674 | einj_debug_dir, &vendor_blob); | ||
| 675 | if (!fentry) | ||
| 676 | goto err_unmap; | ||
| 677 | fentry = debugfs_create_x32("vendor_flags", S_IRUSR | S_IWUSR, | ||
| 678 | einj_debug_dir, &vendor_flags); | ||
| 679 | if (!fentry) | ||
| 680 | goto err_unmap; | ||
| 529 | } | 681 | } |
| 530 | 682 | ||
| 531 | pr_info(EINJ_PFX "Error INJection is initialized.\n"); | 683 | pr_info(EINJ_PFX "Error INJection is initialized.\n"); |
diff --git a/include/acpi/actbl1.h b/include/acpi/actbl1.h index 7504bc99b29b..f25d7efee630 100644 --- a/include/acpi/actbl1.h +++ b/include/acpi/actbl1.h | |||
| @@ -228,7 +228,8 @@ enum acpi_einj_actions { | |||
| 228 | ACPI_EINJ_EXECUTE_OPERATION = 5, | 228 | ACPI_EINJ_EXECUTE_OPERATION = 5, |
| 229 | ACPI_EINJ_CHECK_BUSY_STATUS = 6, | 229 | ACPI_EINJ_CHECK_BUSY_STATUS = 6, |
| 230 | ACPI_EINJ_GET_COMMAND_STATUS = 7, | 230 | ACPI_EINJ_GET_COMMAND_STATUS = 7, |
| 231 | ACPI_EINJ_ACTION_RESERVED = 8, /* 8 and greater are reserved */ | 231 | ACPI_EINJ_SET_ERROR_TYPE_WITH_ADDRESS = 8, |
| 232 | ACPI_EINJ_ACTION_RESERVED = 9, /* 9 and greater are reserved */ | ||
| 232 | ACPI_EINJ_TRIGGER_ERROR = 0xFF /* Except for this value */ | 233 | ACPI_EINJ_TRIGGER_ERROR = 0xFF /* Except for this value */ |
| 233 | }; | 234 | }; |
| 234 | 235 | ||
