diff options
| -rw-r--r-- | arch/x86/include/asm/apicdef.h | 1 | ||||
| -rw-r--r-- | arch/x86/kernel/apic/apic.c | 83 |
2 files changed, 75 insertions, 9 deletions
diff --git a/arch/x86/include/asm/apicdef.h b/arch/x86/include/asm/apicdef.h index 7fe3b3060f08..a859ca461fb0 100644 --- a/arch/x86/include/asm/apicdef.h +++ b/arch/x86/include/asm/apicdef.h | |||
| @@ -131,6 +131,7 @@ | |||
| 131 | #define APIC_EILVTn(n) (0x500 + 0x10 * n) | 131 | #define APIC_EILVTn(n) (0x500 + 0x10 * n) |
| 132 | #define APIC_EILVT_NR_AMD_K8 1 /* # of extended interrupts */ | 132 | #define APIC_EILVT_NR_AMD_K8 1 /* # of extended interrupts */ |
| 133 | #define APIC_EILVT_NR_AMD_10H 4 | 133 | #define APIC_EILVT_NR_AMD_10H 4 |
| 134 | #define APIC_EILVT_NR_MAX APIC_EILVT_NR_AMD_10H | ||
| 134 | #define APIC_EILVT_LVTOFF(x) (((x) >> 4) & 0xF) | 135 | #define APIC_EILVT_LVTOFF(x) (((x) >> 4) & 0xF) |
| 135 | #define APIC_EILVT_MSG_FIX 0x0 | 136 | #define APIC_EILVT_MSG_FIX 0x0 |
| 136 | #define APIC_EILVT_MSG_SMI 0x2 | 137 | #define APIC_EILVT_MSG_SMI 0x2 |
diff --git a/arch/x86/kernel/apic/apic.c b/arch/x86/kernel/apic/apic.c index 8cf86fb3b4e3..2bfeafd24f5c 100644 --- a/arch/x86/kernel/apic/apic.c +++ b/arch/x86/kernel/apic/apic.c | |||
| @@ -52,6 +52,7 @@ | |||
| 52 | #include <asm/mce.h> | 52 | #include <asm/mce.h> |
| 53 | #include <asm/kvm_para.h> | 53 | #include <asm/kvm_para.h> |
| 54 | #include <asm/tsc.h> | 54 | #include <asm/tsc.h> |
| 55 | #include <asm/atomic.h> | ||
| 55 | 56 | ||
| 56 | unsigned int num_processors; | 57 | unsigned int num_processors; |
| 57 | 58 | ||
| @@ -370,24 +371,88 @@ static void __setup_APIC_LVTT(unsigned int clocks, int oneshot, int irqen) | |||
| 370 | } | 371 | } |
| 371 | 372 | ||
| 372 | /* | 373 | /* |
| 373 | * Setup extended LVT, AMD specific (K8, family 10h) | 374 | * Setup extended LVT, AMD specific |
| 374 | * | 375 | * |
| 375 | * Vector mappings are hard coded. On K8 only offset 0 (APIC500) and | 376 | * Software should use the LVT offsets the BIOS provides. The offsets |
| 376 | * MCE interrupts are supported. Thus MCE offset must be set to 0. | 377 | * are determined by the subsystems using it like those for MCE |
| 378 | * threshold or IBS. On K8 only offset 0 (APIC500) and MCE interrupts | ||
| 379 | * are supported. Beginning with family 10h at least 4 offsets are | ||
| 380 | * available. | ||
| 377 | * | 381 | * |
| 378 | * If mask=1, the LVT entry does not generate interrupts while mask=0 | 382 | * Since the offsets must be consistent for all cores, we keep track |
| 379 | * enables the vector. See also the BKDGs. | 383 | * of the LVT offsets in software and reserve the offset for the same |
| 384 | * vector also to be used on other cores. An offset is freed by | ||
| 385 | * setting the entry to APIC_EILVT_MASKED. | ||
| 386 | * | ||
| 387 | * If the BIOS is right, there should be no conflicts. Otherwise a | ||
| 388 | * "[Firmware Bug]: ..." error message is generated. However, if | ||
| 389 | * software does not properly determines the offsets, it is not | ||
| 390 | * necessarily a BIOS bug. | ||
| 380 | */ | 391 | */ |
| 381 | 392 | ||
| 382 | #define APIC_EILVT_LVTOFF_MCE 0 | 393 | #define APIC_EILVT_LVTOFF_MCE 0 |
| 383 | #define APIC_EILVT_LVTOFF_IBS 1 | 394 | #define APIC_EILVT_LVTOFF_IBS 1 |
| 384 | 395 | ||
| 385 | static void setup_APIC_eilvt(u8 lvt_off, u8 vector, u8 msg_type, u8 mask) | 396 | static atomic_t eilvt_offsets[APIC_EILVT_NR_MAX]; |
| 397 | |||
| 398 | static inline int eilvt_entry_is_changeable(unsigned int old, unsigned int new) | ||
| 399 | { | ||
| 400 | return (old & APIC_EILVT_MASKED) | ||
| 401 | || (new == APIC_EILVT_MASKED) | ||
| 402 | || ((new & ~APIC_EILVT_MASKED) == old); | ||
| 403 | } | ||
| 404 | |||
| 405 | static unsigned int reserve_eilvt_offset(int offset, unsigned int new) | ||
| 406 | { | ||
| 407 | unsigned int rsvd; /* 0: uninitialized */ | ||
| 408 | |||
| 409 | if (offset >= APIC_EILVT_NR_MAX) | ||
| 410 | return ~0; | ||
| 411 | |||
| 412 | rsvd = atomic_read(&eilvt_offsets[offset]) & ~APIC_EILVT_MASKED; | ||
| 413 | do { | ||
| 414 | if (rsvd && | ||
| 415 | !eilvt_entry_is_changeable(rsvd, new)) | ||
| 416 | /* may not change if vectors are different */ | ||
| 417 | return rsvd; | ||
| 418 | rsvd = atomic_cmpxchg(&eilvt_offsets[offset], rsvd, new); | ||
| 419 | } while (rsvd != new); | ||
| 420 | |||
| 421 | return new; | ||
| 422 | } | ||
| 423 | |||
| 424 | /* | ||
| 425 | * If mask=1, the LVT entry does not generate interrupts while mask=0 | ||
| 426 | * enables the vector. See also the BKDGs. | ||
| 427 | */ | ||
| 428 | |||
| 429 | static int setup_APIC_eilvt(u8 offset, u8 vector, u8 msg_type, u8 mask) | ||
| 386 | { | 430 | { |
| 387 | unsigned long reg = (lvt_off << 4) + APIC_EILVTn(0); | 431 | unsigned long reg = APIC_EILVTn(offset); |
| 388 | unsigned int v = (mask << 16) | (msg_type << 8) | vector; | 432 | unsigned int new, old, reserved; |
| 433 | |||
| 434 | new = (mask << 16) | (msg_type << 8) | vector; | ||
| 435 | old = apic_read(reg); | ||
| 436 | reserved = reserve_eilvt_offset(offset, new); | ||
| 389 | 437 | ||
| 390 | apic_write(reg, v); | 438 | if (reserved != new) { |
| 439 | pr_err(FW_BUG "cpu %d, try to setup vector 0x%x, but " | ||
| 440 | "vector 0x%x was already reserved by another core, " | ||
| 441 | "APIC%lX=0x%x\n", | ||
| 442 | smp_processor_id(), new, reserved, reg, old); | ||
| 443 | return -EINVAL; | ||
| 444 | } | ||
| 445 | |||
| 446 | if (!eilvt_entry_is_changeable(old, new)) { | ||
| 447 | pr_err(FW_BUG "cpu %d, try to setup vector 0x%x but " | ||
| 448 | "register already in use, APIC%lX=0x%x\n", | ||
| 449 | smp_processor_id(), new, reg, old); | ||
| 450 | return -EBUSY; | ||
| 451 | } | ||
| 452 | |||
| 453 | apic_write(reg, new); | ||
| 454 | |||
| 455 | return 0; | ||
| 391 | } | 456 | } |
| 392 | 457 | ||
| 393 | u8 setup_APIC_eilvt_mce(u8 vector, u8 msg_type, u8 mask) | 458 | u8 setup_APIC_eilvt_mce(u8 vector, u8 msg_type, u8 mask) |
