Skip to content

Commit

Permalink
Merge branch 'x86-apic-for-linus' of git://git.kernel.org/pub/scm/lin…
Browse files Browse the repository at this point in the history
…ux/kernel/git/tip/tip

Pull x96 apic updates from Thomas Gleixner:
 "Updates for the x86 APIC interrupt handling and APIC timer:

   - Fix a long standing issue with spurious interrupts which was caused
     by the big vector management rework a few years ago. Robert Hodaszi
     provided finally enough debug data and an excellent initial failure
     analysis which allowed to understand the underlying issues.

     This contains a change to the core interrupt management code which
     is required to handle this correctly for the APIC/IO_APIC. The core
     changes are NOOPs for most architectures except ARM64. ARM64 is not
     impacted by the change as confirmed by Marc Zyngier.

   - Newer systems allow to disable the PIT clock for power saving
     causing panic in the timer interrupt delivery check of the IO/APIC
     when the HPET timer is not enabled either. While the clock could be
     turned on this would cause an endless whack a mole game to chase
     the proper register in each affected chipset.

     These systems provide the relevant frequencies for TSC, CPU and the
     local APIC timer via CPUID and/or MSRs, which allows to avoid the
     PIT/HPET based calibration. As the calibration code is the only
     usage of the legacy timers on modern systems and is skipped anyway
     when the frequencies are known already, there is no point in
     setting up the PIT and actually checking for the interrupt delivery
     via IO/APIC.

     To achieve this on a wide variety of platforms, the CPUID/MSR based
     frequency readout has been made more robust, which also allowed to
     remove quite some workarounds which turned out to be not longer
     required. Thanks to Daniel Drake for analysis, patches and
     verification"

* 'x86-apic-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  x86/irq: Seperate unused system vectors from spurious entry again
  x86/irq: Handle spurious interrupt after shutdown gracefully
  x86/ioapic: Implement irq_get_irqchip_state() callback
  genirq: Add optional hardware synchronization for shutdown
  genirq: Fix misleading synchronize_irq() documentation
  genirq: Delay deactivation in free_irq()
  x86/timer: Skip PIT initialization on modern chipsets
  x86/apic: Use non-atomic operations when possible
  x86/apic: Make apic_bsp_setup() static
  x86/tsc: Set LAPIC timer period to crystal clock frequency
  x86/apic: Rename 'lapic_timer_frequency' to 'lapic_timer_period'
  x86/tsc: Use CPUID.0x16 to calculate missing crystal frequency
  • Loading branch information
torvalds committed Jul 8, 2019
2 parents 927ba67 + f8a8fe6 commit 0902d50
Show file tree
Hide file tree
Showing 25 changed files with 335 additions and 94 deletions.
24 changes: 24 additions & 0 deletions arch/x86/entry/entry_32.S
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,30 @@ ENTRY(irq_entries_start)
.endr
END(irq_entries_start)

#ifdef CONFIG_X86_LOCAL_APIC
.align 8
ENTRY(spurious_entries_start)
vector=FIRST_SYSTEM_VECTOR
.rept (NR_VECTORS - FIRST_SYSTEM_VECTOR)
pushl $(~vector+0x80) /* Note: always in signed byte range */
vector=vector+1
jmp common_spurious
.align 8
.endr
END(spurious_entries_start)

common_spurious:
ASM_CLAC
addl $-0x80, (%esp) /* Adjust vector into the [-256, -1] range */
SAVE_ALL switch_stacks=1
ENCODE_FRAME_POINTER
TRACE_IRQS_OFF
movl %esp, %eax
call smp_spurious_interrupt
jmp ret_from_intr
ENDPROC(common_interrupt)
#endif

/*
* the CPU automatically disables interrupts when executing an IRQ vector,
* so IRQ-flags tracing has to follow that:
Expand Down
30 changes: 26 additions & 4 deletions arch/x86/entry/entry_64.S
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,18 @@ ENTRY(irq_entries_start)
.endr
END(irq_entries_start)

.align 8
ENTRY(spurious_entries_start)
vector=FIRST_SYSTEM_VECTOR
.rept (NR_VECTORS - FIRST_SYSTEM_VECTOR)
UNWIND_HINT_IRET_REGS
pushq $(~vector+0x80) /* Note: always in signed byte range */
jmp common_spurious
.align 8
vector=vector+1
.endr
END(spurious_entries_start)

.macro DEBUG_ENTRY_ASSERT_IRQS_OFF
#ifdef CONFIG_DEBUG_ENTRY
pushq %rax
Expand Down Expand Up @@ -571,10 +583,20 @@ _ASM_NOKPROBE(interrupt_entry)

/* Interrupt entry/exit. */

/*
* The interrupt stubs push (~vector+0x80) onto the stack and
* then jump to common_interrupt.
*/
/*
* The interrupt stubs push (~vector+0x80) onto the stack and
* then jump to common_spurious/interrupt.
*/
common_spurious:
addq $-0x80, (%rsp) /* Adjust vector to [-256, -1] range */
call interrupt_entry
UNWIND_HINT_REGS indirect=1
call smp_spurious_interrupt /* rdi points to pt_regs */
jmp ret_from_intr
END(common_spurious)
_ASM_NOKPROBE(common_spurious)

/* common_interrupt is a hotpath. Align it */
.p2align CONFIG_X86_L1_CACHE_SHIFT
common_interrupt:
addq $-0x80, (%rsp) /* Adjust vector to [-256, -1] range */
Expand Down
5 changes: 3 additions & 2 deletions arch/x86/include/asm/apic.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ extern unsigned int apic_verbosity;
extern int local_apic_timer_c2_ok;

extern int disable_apic;
extern unsigned int lapic_timer_frequency;
extern unsigned int lapic_timer_period;

extern enum apic_intr_mode_id apic_intr_mode;
enum apic_intr_mode_id {
Expand Down Expand Up @@ -155,7 +155,6 @@ static inline int apic_force_enable(unsigned long addr)
extern int apic_force_enable(unsigned long addr);
#endif

extern void apic_bsp_setup(bool upmode);
extern void apic_ap_setup(void);

/*
Expand All @@ -175,6 +174,7 @@ extern void lapic_assign_system_vectors(void);
extern void lapic_assign_legacy_vector(unsigned int isairq, bool replace);
extern void lapic_online(void);
extern void lapic_offline(void);
extern bool apic_needs_pit(void);

#else /* !CONFIG_X86_LOCAL_APIC */
static inline void lapic_shutdown(void) { }
Expand All @@ -188,6 +188,7 @@ static inline void init_bsp_APIC(void) { }
static inline void apic_intr_mode_init(void) { }
static inline void lapic_assign_system_vectors(void) { }
static inline void lapic_assign_legacy_vector(unsigned int i, bool r) { }
static inline bool apic_needs_pit(void) { return true; }
#endif /* !CONFIG_X86_LOCAL_APIC */

#ifdef CONFIG_X86_X2APIC
Expand Down
5 changes: 4 additions & 1 deletion arch/x86/include/asm/hw_irq.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,11 @@ extern char irq_entries_start[];
#define trace_irq_entries_start irq_entries_start
#endif

extern char spurious_entries_start[];

#define VECTOR_UNUSED NULL
#define VECTOR_RETRIGGERED ((void *)~0UL)
#define VECTOR_SHUTDOWN ((void *)~0UL)
#define VECTOR_RETRIGGERED ((void *)~1UL)

typedef struct irq_desc* vector_irq_t[NR_VECTORS];
DECLARE_PER_CPU(vector_irq_t, vector_irq);
Expand Down
1 change: 1 addition & 0 deletions arch/x86/include/asm/time.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

extern void hpet_time_init(void);
extern void time_init(void);
extern bool pit_timer_init(void);

extern struct clock_event_device *global_clock_event;

Expand Down
87 changes: 62 additions & 25 deletions arch/x86/kernel/apic/apic.c
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ static struct resource lapic_resource = {
.flags = IORESOURCE_MEM | IORESOURCE_BUSY,
};

unsigned int lapic_timer_frequency = 0;
unsigned int lapic_timer_period = 0;

static void apic_pm_activate(void);

Expand Down Expand Up @@ -501,7 +501,7 @@ lapic_timer_set_periodic_oneshot(struct clock_event_device *evt, bool oneshot)
if (evt->features & CLOCK_EVT_FEAT_DUMMY)
return 0;

__setup_APIC_LVTT(lapic_timer_frequency, oneshot, 1);
__setup_APIC_LVTT(lapic_timer_period, oneshot, 1);
return 0;
}

Expand Down Expand Up @@ -805,11 +805,11 @@ calibrate_by_pmtimer(long deltapm, long *delta, long *deltatsc)

static int __init lapic_init_clockevent(void)
{
if (!lapic_timer_frequency)
if (!lapic_timer_period)
return -1;

/* Calculate the scaled math multiplication factor */
lapic_clockevent.mult = div_sc(lapic_timer_frequency/APIC_DIVISOR,
lapic_clockevent.mult = div_sc(lapic_timer_period/APIC_DIVISOR,
TICK_NSEC, lapic_clockevent.shift);
lapic_clockevent.max_delta_ns =
clockevent_delta2ns(0x7FFFFFFF, &lapic_clockevent);
Expand All @@ -821,6 +821,33 @@ static int __init lapic_init_clockevent(void)
return 0;
}

bool __init apic_needs_pit(void)
{
/*
* If the frequencies are not known, PIT is required for both TSC
* and apic timer calibration.
*/
if (!tsc_khz || !cpu_khz)
return true;

/* Is there an APIC at all? */
if (!boot_cpu_has(X86_FEATURE_APIC))
return true;

/* Deadline timer is based on TSC so no further PIT action required */
if (boot_cpu_has(X86_FEATURE_TSC_DEADLINE_TIMER))
return false;

/* APIC timer disabled? */
if (disable_apic_timer)
return true;
/*
* The APIC timer frequency is known already, no PIT calibration
* required. If unknown, let the PIT be initialized.
*/
return lapic_timer_period == 0;
}

static int __init calibrate_APIC_clock(void)
{
struct clock_event_device *levt = this_cpu_ptr(&lapic_events);
Expand All @@ -839,7 +866,7 @@ static int __init calibrate_APIC_clock(void)
*/
if (!lapic_init_clockevent()) {
apic_printk(APIC_VERBOSE, "lapic timer already calibrated %d\n",
lapic_timer_frequency);
lapic_timer_period);
/*
* Direct calibration methods must have an always running
* local APIC timer, no need for broadcast timer.
Expand Down Expand Up @@ -884,13 +911,13 @@ static int __init calibrate_APIC_clock(void)
pm_referenced = !calibrate_by_pmtimer(lapic_cal_pm2 - lapic_cal_pm1,
&delta, &deltatsc);

lapic_timer_frequency = (delta * APIC_DIVISOR) / LAPIC_CAL_LOOPS;
lapic_timer_period = (delta * APIC_DIVISOR) / LAPIC_CAL_LOOPS;
lapic_init_clockevent();

apic_printk(APIC_VERBOSE, "..... delta %ld\n", delta);
apic_printk(APIC_VERBOSE, "..... mult: %u\n", lapic_clockevent.mult);
apic_printk(APIC_VERBOSE, "..... calibration result: %u\n",
lapic_timer_frequency);
lapic_timer_period);

if (boot_cpu_has(X86_FEATURE_TSC)) {
apic_printk(APIC_VERBOSE, "..... CPU clock speed is "
Expand All @@ -901,13 +928,13 @@ static int __init calibrate_APIC_clock(void)

apic_printk(APIC_VERBOSE, "..... host bus clock speed is "
"%u.%04u MHz.\n",
lapic_timer_frequency / (1000000 / HZ),
lapic_timer_frequency % (1000000 / HZ));
lapic_timer_period / (1000000 / HZ),
lapic_timer_period % (1000000 / HZ));

/*
* Do a sanity check on the APIC calibration result
*/
if (lapic_timer_frequency < (1000000 / HZ)) {
if (lapic_timer_period < (1000000 / HZ)) {
local_irq_enable();
pr_warning("APIC frequency too slow, disabling apic timer\n");
return -1;
Expand Down Expand Up @@ -1351,6 +1378,8 @@ void __init init_bsp_APIC(void)
apic_write(APIC_LVT1, value);
}

static void __init apic_bsp_setup(bool upmode);

/* Init the interrupt delivery mode for the BSP */
void __init apic_intr_mode_init(void)
{
Expand Down Expand Up @@ -2041,21 +2070,32 @@ __visible void __irq_entry smp_spurious_interrupt(struct pt_regs *regs)
entering_irq();
trace_spurious_apic_entry(vector);

inc_irq_stat(irq_spurious_count);

/*
* If this is a spurious interrupt then do not acknowledge
*/
if (vector == SPURIOUS_APIC_VECTOR) {
/* See SDM vol 3 */
pr_info("Spurious APIC interrupt (vector 0xFF) on CPU#%d, should never happen.\n",
smp_processor_id());
goto out;
}

/*
* Check if this really is a spurious interrupt and ACK it
* if it is a vectored one. Just in case...
* Spurious interrupts should not be ACKed.
* If it is a vectored one, verify it's set in the ISR. If set,
* acknowledge it.
*/
v = apic_read(APIC_ISR + ((vector & ~0x1f) >> 1));
if (v & (1 << (vector & 0x1f)))
if (v & (1 << (vector & 0x1f))) {
pr_info("Spurious interrupt (vector 0x%02x) on CPU#%d. Acked\n",
vector, smp_processor_id());
ack_APIC_irq();

inc_irq_stat(irq_spurious_count);

/* see sw-dev-man vol 3, chapter 7.4.13.5 */
pr_info("spurious APIC interrupt through vector %02x on CPU#%d, "
"should never happen.\n", vector, smp_processor_id());

} else {
pr_info("Spurious interrupt (vector 0x%02x) on CPU#%d. Not pending!\n",
vector, smp_processor_id());
}
out:
trace_spurious_apic_exit(vector);
exiting_irq();
}
Expand Down Expand Up @@ -2416,11 +2456,8 @@ static void __init apic_bsp_up_setup(void)
/**
* apic_bsp_setup - Setup function for local apic and io-apic
* @upmode: Force UP mode (for APIC_init_uniprocessor)
*
* Returns:
* apic_id of BSP APIC
*/
void __init apic_bsp_setup(bool upmode)
static void __init apic_bsp_setup(bool upmode)
{
connect_bsp_APIC();
if (upmode)
Expand Down
4 changes: 2 additions & 2 deletions arch/x86/kernel/apic/apic_flat_64.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ flat_send_IPI_mask_allbutself(const struct cpumask *cpumask, int vector)
int cpu = smp_processor_id();

if (cpu < BITS_PER_LONG)
clear_bit(cpu, &mask);
__clear_bit(cpu, &mask);

_flat_send_IPI_mask(mask, vector);
}
Expand All @@ -92,7 +92,7 @@ static void flat_send_IPI_allbutself(int vector)
unsigned long mask = cpumask_bits(cpu_online_mask)[0];

if (cpu < BITS_PER_LONG)
clear_bit(cpu, &mask);
__clear_bit(cpu, &mask);

_flat_send_IPI_mask(mask, vector);
}
Expand Down
Loading

0 comments on commit 0902d50

Please sign in to comment.