--- zzzz-none-000/linux-4.9.279/arch/x86/kernel/hpet.c 2021-08-08 06:38:54.000000000 +0000 +++ puma7-atom-6591-750/linux-4.9.279/arch/x86/kernel/hpet.c 2023-02-08 11:43:42.000000000 +0000 @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include @@ -182,6 +184,151 @@ static void hpet_reserve_msi_timers(struct hpet_data *hd); +#if IS_ENABLED(CONFIG_HPET_NMI_TIMER) +#include + +static void hpet_stop_counter(void); +static void hpet_reset_counter(void); +static void hpet_start_counter(void); + +#define AVM_TIMER_NR 2 +static int avm_timer_irq = -ENODEV; + +static unsigned int avm_ticks; + +static irqreturn_t dummy_interrupt_handler(int irq, void *data) +{ + return IRQ_HANDLED; +} + +static int avm_nmi_timer_request_irq(int timer) +{ + unsigned long int_route_cap; + int irq, ret; + + int_route_cap = hpet_readl(HPET_Tn_CFG(timer)+4); + + /* Try legacy interrupts only, since we are edge-triggered */ + int_route_cap &= 0xffff; + + ret = -ENODEV; + for_each_set_bit(irq, &int_route_cap, HPET_MAX_IRQ) { + ret = request_irq(irq, dummy_interrupt_handler, + IRQF_TIMER | IRQF_NOBALANCING, + "HPET NMI dummy", NULL); + if (ret == 0) + return irq; + } + + return ret; +} + +static void avm_nmi_timer_init(void) +{ + unsigned int cfg; + int irq; + + irq = avm_nmi_timer_request_irq(AVM_TIMER_NR); + if (irq < 0) { + pr_err("hpet: Failed to register irq for avm watchdog: %d\n", irq); + return; + } + + /* + * Program the timer from scratch so that we're independent of + * changes in mainline code. + */ + cfg = hpet_readl(HPET_Tn_CFG(AVM_TIMER_NR)); + cfg &= ~(HPET_TN_ENABLE | HPET_TN_LEVEL | HPET_TN_FSB + | (HPET_TN_ROUTE << HPET_TN_ROUTE_SHIFT)); + cfg |= HPET_TN_32BIT | (irq << HPET_TN_ROUTE_SHIFT); + hpet_writel(cfg, HPET_Tn_CFG(AVM_TIMER_NR)); + + pr_info("Using HPET timer %d for AVM NMI watchdog, irq %d.\n", + AVM_TIMER_NR, irq); + + hpet_print_config(); + avm_timer_irq = irq; +} + +void avm_nmi_timer_update(void) +{ + unsigned int cmp, now; + + now = hpet_readl(HPET_COUNTER); + cmp = now + avm_ticks; + + hpet_writel(cmp, HPET_Tn_CMP(AVM_TIMER_NR)); +} +EXPORT_SYMBOL(avm_nmi_timer_update); + +int avm_nmi_timer_start(int seconds, const cpumask_t *mask) +{ + unsigned long long ticks_per_sec; + unsigned int cfg, period; + int result; + + result = 0; + if (seconds <= 0) { + result = -EINVAL; + goto err_out; + } + + if (avm_timer_irq < 0) + return -ENODEV; + + result = IO_APIC_enable_nmi(avm_timer_irq, mask); + if (result != 0) { + pr_err("[%s] Enabling NMI failed.\n", __func__); + goto err_out; + } + + period = hpet_readl(HPET_PERIOD); + ticks_per_sec = DIV_ROUND_CLOSEST_ULL(FSEC_PER_SEC, period); + avm_ticks = seconds * ticks_per_sec; + + pr_info("[%s] Starting AVM NMI timer on cpus #%*pbl. Period %d seconds, %d ticks\n", + __func__, cpumask_pr_args(mask), seconds, avm_ticks); + + avm_nmi_timer_update(); + cfg = hpet_readl(HPET_Tn_CFG(AVM_TIMER_NR)); + cfg |= HPET_TN_ENABLE; + + hpet_writel(cfg, HPET_Tn_CFG(AVM_TIMER_NR)); + hpet_start_counter(); + +err_out: + return result; +} +EXPORT_SYMBOL(avm_nmi_timer_start); + +void avm_nmi_timer_stop(void) +{ + unsigned int cfg; + + cfg = hpet_readl(HPET_Tn_CFG(AVM_TIMER_NR)); + cfg &= ~(HPET_TN_ENABLE); + + hpet_writel(cfg, HPET_Tn_CFG(AVM_TIMER_NR)); + if (avm_timer_irq >= 0) { + IO_APIC_disable_nmi(avm_timer_irq); + avm_timer_irq = -ENODEV; + } +} +EXPORT_SYMBOL(avm_nmi_timer_stop); + +bool avm_nmi_timer_expired(void) +{ + unsigned long cmp, now; + + now = hpet_readl(HPET_COUNTER); + cmp = hpet_readl(HPET_Tn_CMP(AVM_TIMER_NR)); + + return time_after_eq(now, cmp); +} +EXPORT_SYMBOL(avm_nmi_timer_expired); +#endif /* IS_ENABLED(CONFIG_HPET_NMI_TIMER) */ + static void hpet_reserve_platform_timers(unsigned int id) { struct hpet __iomem *hpet = hpet_virt_address; @@ -201,6 +348,10 @@ hpet_reserve_timer(&hd, 1); #endif +#if IS_ENABLED(CONFIG_HPET_NMI_TIMER) + hpet_reserve_timer(&hd, AVM_TIMER_NR); +#endif + /* * NOTE that hd_irq[] reflects IOAPIC input pins (LEGACY_8254 * is wrong for i8259!) not the output IRQ. Many BIOS writers @@ -999,6 +1150,11 @@ pr_warn("HPET: Unrecognized bits %#x set in cfg#%u\n", cfg, i); } + +#if defined(CONFIG_HPET_NMI_TIMER) + avm_nmi_timer_init(); +#endif + hpet_print_config(); if (hpet_clocksource_register())