diff options
Diffstat (limited to 'hw/serial.c')
-rw-r--r-- | hw/serial.c | 55 |
1 files changed, 49 insertions, 6 deletions
diff --git a/hw/serial.c b/hw/serial.c index b70d739622..b5ac6f1dea 100644 --- 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,8 @@ struct SerialState { int last_break_enable; target_phys_addr_t base; int it_shift; + QEMUTimer *tx_timer; + int tx_burst; }; static void serial_receive_byte(SerialState *s, int ch); @@ -111,6 +121,28 @@ static void serial_update_irq(SerialState *s) } } +static void serial_tx_done(void *opaque) +{ + SerialState *s = opaque; + + if (s->tx_burst < 0) { + uint16_t divider; + + if (s->divider) + divider = s->divider; + else + divider = 1; + + /* We assume 10 bits/char, OK for this purpose. */ + s->tx_burst = THROTTLE_TX_INTERVAL * 1000 / + (1000000 * 10 / (115200 / divider)); + } + s->thr_ipending = 1; + s->lsr |= UART_LSR_THRE; + s->lsr |= UART_LSR_TEMT; + serial_update_irq(s); +} + static void serial_update_parameters(SerialState *s) { int speed, parity, data_bits, stop_bits; @@ -166,15 +198,18 @@ static void serial_ioport_write(void *opaque, uint32_t addr, uint32_t val) if (!(s->mcr & UART_MCR_LOOP)) { /* when not in loopback mode, send the char */ qemu_chr_write(s->chr, &ch, 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) { + } else { /* 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; case 1: @@ -387,6 +422,10 @@ SerialState *serial_init(int base, qemu_irq irq, CharDriverState *chr) 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 +525,10 @@ SerialState *serial_mm_init (target_phys_addr_t base, int it_shift, 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); |