diff --git a/xen/common/irq.c b/xen/common/irq.c index 29729349a6..b76bbb6143 100644 --- a/xen/common/irq.c +++ b/xen/common/irq.c @@ -54,3 +54,15 @@ unsigned int cf_check irq_startup_none(struct irq_desc *desc) { return 0; } + +void disable_irq(unsigned int irq) +{ + struct irq_desc *desc = irq_to_desc(irq); + unsigned long flags; + + spin_lock_irqsave(&desc->lock, flags); + if ( desc->handler->disable ) + desc->handler->disable(desc); + desc->status |= IRQ_DISABLED; + spin_unlock_irqrestore(&desc->lock, flags); +} diff --git a/xen/drivers/char/ns16550.c b/xen/drivers/char/ns16550.c index df554001bc..914e9e17e8 100644 --- a/xen/drivers/char/ns16550.c +++ b/xen/drivers/char/ns16550.c @@ -62,6 +62,7 @@ static struct ns16550 { #endif unsigned int timeout_ms; bool intr_works; + bool force_polling; bool dw_usr_bsy; #ifdef NS16550_PCI /* PCI card parameters. */ @@ -190,12 +191,38 @@ static void cf_check ns16550_interrupt(int irq, void *dev_id) { struct serial_port *port = dev_id; struct ns16550 *uart = port->uart; + /* + * Set quite arbitrarily as 4x the time to drain the TX or fill RX FIFOs, + * set the upper bound as 5ms or the timeout_ms value, whatever is higher. + */ + const unsigned int delta = min(uart->timeout_ms * 4, + max(5u, uart->timeout_ms)); + const s_time_t timeout = NOW() + MILLISECS(delta); + ASSERT(!uart->force_polling); uart->intr_works = 1; while ( !(ns_read_reg(uart, UART_IIR) & UART_IIR_NOINT) ) { u8 lsr = ns_read_reg(uart, UART_LSR); + s_time_t now = NOW(); + + /* Break out of the loop if spending too much time. */ + if ( now > timeout ) + { + /* Disable the interrupt source - it's never shared. */ + disable_irq(irq); + + /* Disable interrupt generation on the device and arm the timer. */ + uart->force_polling = true; + ns_write_reg(uart, UART_IER, 0); + set_timer(&uart->timer, now + MILLISECS(uart->timeout_ms)); + printk(XENLOG_WARNING + "uart interrupt taking more than %ums, switched to polling\n", + delta); + + return; + } if ( (lsr & uart->lsr_mask) == uart->lsr_mask ) serial_tx_interrupt(port); @@ -223,7 +250,7 @@ static void cf_check __ns16550_poll(const struct cpu_user_regs *regs) struct ns16550 *uart = port->uart; const struct cpu_user_regs *old_regs; - if ( uart->intr_works ) + if ( uart->intr_works && !uart->force_polling ) return; /* Interrupts work - no more polling */ /* Mimic interrupt context. */ @@ -322,6 +349,7 @@ static void ns16550_setup_preirq(struct ns16550 *uart) unsigned int divisor; uart->intr_works = 0; + uart->force_polling = false; pci_serial_early_init(uart); diff --git a/xen/include/xen/irq.h b/xen/include/xen/irq.h index 95034c0d6b..30d58e372b 100644 --- a/xen/include/xen/irq.h +++ b/xen/include/xen/irq.h @@ -121,6 +121,7 @@ extern void release_irq(unsigned int irq, const void *dev_id); extern int request_irq(unsigned int irq, unsigned int irqflags, void (*handler)(int irq, void *dev_id), const char *devname, void *dev_id); +void disable_irq(unsigned int irq); extern const hw_irq_controller no_irq_type; void cf_check no_action(int cpl, void *dev_id);