From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1Jkxah-0000Dk-D6 for qemu-devel@nongnu.org; Sun, 13 Apr 2008 04:29:35 -0400 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1Jkxag-0000DD-7i for qemu-devel@nongnu.org; Sun, 13 Apr 2008 04:29:34 -0400 Received: from [199.232.76.173] (helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1Jkxaf-0000D4-To for qemu-devel@nongnu.org; Sun, 13 Apr 2008 04:29:34 -0400 Received: from fmmailgate03.web.de ([217.72.192.234]) by monty-python.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1Jkxaf-00020U-Gu for qemu-devel@nongnu.org; Sun, 13 Apr 2008 04:29:33 -0400 Message-ID: <4801C46A.5010403@web.de> Date: Sun, 13 Apr 2008 10:29:30 +0200 From: Jan Kiszka MIME-Version: 1.0 Content-Type: text/plain; charset=ISO-8859-15 Content-Transfer-Encoding: 7bit Sender: jan.kiszka@web.de Subject: [Qemu-devel] [PATCH 1/2] 8250: throttle TX-completion IRQs Reply-To: qemu-devel@nongnu.org List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org Cc: Paul Brook The 8250 UART emulation currently raises a TX-done IRQ immediately when the guest writes out some character. This is problematic for guests like Linux which may flush its output buffer in a loop from IRQ context, because they may then enter a tight loop with IRQs disabled. In fact, Linux breaks out of this loop after some iterations and issue the well-known [1] "too much work for irq..." warning. And in case the console output is on the very same serial port, the console output is utterly corrupted. Patch below addresses the issue by delaying TX-completion IRQs more realistically. In contrast to the first version, this one inserts a delay of 10 ms (ie. a delay even course-grained system timers can deal with) after the corresponding number of characters have been written. Specifically when transmitting a larger number of characters (e.g. a few 100 @115k2), this throttling will reflect delay on real hardware quite precisely - and it avoids the infamous guest overload. [1] http://lkml.org/lkml/2008/1/12/135 Signed-off-by: Jan Kiszka --- hw/serial.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 10 deletions(-) Index: b/hw/serial.c =================================================================== --- a/hw/serial.c +++ b/hw/serial.c @@ -25,6 +25,7 @@ #include "qemu-char.h" #include "isa.h" #include "pc.h" +#include "qemu-timer.h" //#define DEBUG_SERIAL @@ -73,6 +74,13 @@ #define UART_LSR_OE 0x02 /* Overrun error indicator */ #define UART_LSR_DR 0x01 /* Receiver data ready */ +/* + * Delay TX IRQ after sending as much characters as the given interval would + * contain on real hardware. This avoids overloading the guest if it processes + * its output buffer in a loop inside the TX IRQ handler. + */ +#define THROTTLE_TX_INTERVAL 10 /* ms */ + struct SerialState { uint16_t divider; uint8_t rbr; /* receive register */ @@ -91,6 +99,9 @@ struct SerialState { int last_break_enable; target_phys_addr_t base; int it_shift; + QEMUTimer *tx_timer; + int tx_burst; + char tx_buf; }; static void serial_receive_byte(SerialState *s, int ch); @@ -111,6 +122,25 @@ static void serial_update_irq(SerialStat } } +static void serial_tx_done(void *opaque) +{ + SerialState *s = opaque; + + if (s->tx_burst < 0) { + /* We assume 10 bits/char, OK for this purpose. */ + s->tx_burst = THROTTLE_TX_INTERVAL * 1000 / + (1000000 * 10 / (115200 / s->divider)); + } + s->thr_ipending = 1; + s->lsr |= UART_LSR_THRE; + s->lsr |= UART_LSR_TEMT; + serial_update_irq(s); + if (s->mcr & UART_MCR_LOOP) { + /* in loopback mode, say that we just received a char */ + serial_receive_byte(s, s->tx_buf); + } +} + static void serial_update_parameters(SerialState *s) { int speed, parity, data_bits, stop_bits; @@ -146,7 +176,6 @@ static void serial_update_parameters(Ser static void serial_ioport_write(void *opaque, uint32_t addr, uint32_t val) { SerialState *s = opaque; - unsigned char ch; addr &= 7; #ifdef DEBUG_SERIAL @@ -162,18 +191,18 @@ static void serial_ioport_write(void *op s->thr_ipending = 0; s->lsr &= ~UART_LSR_THRE; serial_update_irq(s); - ch = val; + s->tx_buf = val; if (!(s->mcr & UART_MCR_LOOP)) { /* when not in loopback mode, send the char */ - qemu_chr_write(s->chr, &ch, 1); + qemu_chr_write(s->chr, &s->tx_buf, 1); } - s->thr_ipending = 1; - s->lsr |= UART_LSR_THRE; - s->lsr |= UART_LSR_TEMT; - serial_update_irq(s); - if (s->mcr & UART_MCR_LOOP) { - /* in loopback mode, say that we just received a char */ - serial_receive_byte(s, ch); + if (s->tx_burst > 0) { + s->tx_burst--; + serial_tx_done(s); + } else if (s->tx_burst == 0) { + s->tx_burst--; + qemu_mod_timer(s->tx_timer, qemu_get_clock(vm_clock) + + ticks_per_sec * THROTTLE_TX_INTERVAL / 1000); } } break; @@ -387,6 +416,10 @@ SerialState *serial_init(int base, qemu_ return NULL; s->irq = irq; + s->tx_timer = qemu_new_timer(vm_clock, serial_tx_done, s); + if (!s->tx_timer) + return NULL; + qemu_register_reset(serial_reset, s); serial_reset(s); @@ -486,6 +519,10 @@ SerialState *serial_mm_init (target_phys s->base = base; s->it_shift = it_shift; + s->tx_timer = qemu_new_timer(vm_clock, serial_tx_done, s); + if (!s->tx_timer) + return NULL; + qemu_register_reset(serial_reset, s); serial_reset(s);