aboutsummaryrefslogtreecommitdiffstats
path: root/tools/ioemu/hw/serial.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/ioemu/hw/serial.c')
-rw-r--r--tools/ioemu/hw/serial.c126
1 files changed, 119 insertions, 7 deletions
diff --git a/tools/ioemu/hw/serial.c b/tools/ioemu/hw/serial.c
index f36beb209f..b99e774eae 100644
--- a/tools/ioemu/hw/serial.c
+++ b/tools/ioemu/hw/serial.c
@@ -22,6 +22,9 @@
* THE SOFTWARE.
*/
#include "vl.h"
+#include <sys/time.h>
+#include <time.h>
+#include <assert.h>
//#define DEBUG_SERIAL
@@ -70,6 +73,11 @@
#define UART_LSR_OE 0x02 /* Overrun error indicator */
#define UART_LSR_DR 0x01 /* Receiver data ready */
+/* Maximum retries for a single byte transmit. */
+#define WRITE_MAX_SINGLE_RETRIES 3
+/* Maximum retries for a sequence of back-to-back unsuccessful transmits. */
+#define WRITE_MAX_TOTAL_RETRIES 10
+
struct SerialState {
uint8_t divider;
uint8_t rbr; /* receive register */
@@ -90,6 +98,19 @@ struct SerialState {
int last_break_enable;
target_ulong base;
int it_shift;
+
+ /*
+ * If a character transmitted via UART cannot be written to its
+ * destination immediately we remember it here and retry a few times via
+ * a polling timer.
+ * - write_single_retries: Number of write retries for current byte.
+ * - write_total_retries: Number of write retries for back-to-back
+ * unsuccessful transmits.
+ */
+ int write_single_retries;
+ int write_total_retries;
+ char write_chr;
+ QEMUTimer *write_retry_timer;
};
static void serial_update_irq(SerialState *s)
@@ -140,10 +161,98 @@ static void serial_update_parameters(SerialState *s)
#endif
}
+/* Rate limit serial requests so that e.g. grub on a serial console
+ doesn't kill dom0. Simple token bucket. If we get some actual
+ data from the user, instantly refil the bucket. */
+
+/* How long it takes to generate a token, in microseconds. */
+#define TOKEN_PERIOD 1000
+/* Maximum and initial size of token bucket */
+#define TOKENS_MAX 100000
+
+static int tokens_avail;
+
+static void serial_get_token(void)
+{
+ static struct timeval last_refil_time;
+ static int started;
+
+ assert(tokens_avail >= 0);
+ if (!tokens_avail) {
+ struct timeval delta, now;
+ int generated;
+
+ if (!started) {
+ gettimeofday(&last_refil_time, NULL);
+ tokens_avail = TOKENS_MAX;
+ started = 1;
+ return;
+ }
+ retry:
+ gettimeofday(&now, NULL);
+ delta.tv_sec = now.tv_sec - last_refil_time.tv_sec;
+ delta.tv_usec = now.tv_usec - last_refil_time.tv_usec;
+ if (delta.tv_usec < 0) {
+ delta.tv_usec += 1000000;
+ delta.tv_sec--;
+ }
+ assert(delta.tv_usec >= 0 && delta.tv_sec >= 0);
+ if (delta.tv_usec < TOKEN_PERIOD) {
+ struct timespec ts;
+ /* Wait until at least one token is available. */
+ ts.tv_sec = TOKEN_PERIOD / 1000000;
+ ts.tv_nsec = (TOKEN_PERIOD % 1000000) * 1000;
+ while (nanosleep(&ts, &ts) < 0 && errno == EINTR)
+ ;
+ goto retry;
+ }
+ generated = (delta.tv_sec * 1000000) / TOKEN_PERIOD;
+ generated +=
+ ((delta.tv_sec * 1000000) % TOKEN_PERIOD + delta.tv_usec) / TOKEN_PERIOD;
+ assert(generated > 0);
+
+ last_refil_time.tv_usec += (generated * TOKEN_PERIOD) % 1000000;
+ last_refil_time.tv_sec += last_refil_time.tv_usec / 1000000;
+ last_refil_time.tv_usec %= 1000000;
+ last_refil_time.tv_sec += (generated * TOKEN_PERIOD) / 1000000;
+ if (generated > TOKENS_MAX)
+ generated = TOKENS_MAX;
+ tokens_avail = generated;
+ }
+ tokens_avail--;
+}
+
+static void serial_chr_write(void *opaque)
+{
+ SerialState *s = opaque;
+
+ /* Cancel any outstanding retry if this is a new byte. */
+ qemu_del_timer(s->write_retry_timer);
+
+ /* Retry every 100ms for 300ms total. */
+ if (qemu_chr_write(s->chr, &s->write_chr, 1) == -1) {
+ s->write_total_retries++;
+ if (s->write_single_retries++ >= WRITE_MAX_SINGLE_RETRIES)
+ fprintf(stderr, "serial: write error\n");
+ else if (s->write_total_retries <= WRITE_MAX_TOTAL_RETRIES) {
+ qemu_mod_timer(s->write_retry_timer,
+ qemu_get_clock(vm_clock) + ticks_per_sec / 10);
+ return;
+ }
+ } else {
+ s->write_total_retries = 0; /* if successful then reset counter */
+ }
+
+ /* Success: Notify guest that THR is empty. */
+ s->thr_ipending = 1;
+ s->lsr |= UART_LSR_THRE;
+ s->lsr |= UART_LSR_TEMT;
+ serial_update_irq(s);
+}
+
static void serial_ioport_write(void *opaque, uint32_t addr, uint32_t val)
{
SerialState *s = opaque;
- unsigned char ch;
addr &= 7;
#ifdef DEBUG_SERIAL
@@ -159,12 +268,9 @@ static void serial_ioport_write(void *opaque, uint32_t addr, uint32_t val)
s->thr_ipending = 0;
s->lsr &= ~UART_LSR_THRE;
serial_update_irq(s);
- ch = val;
- 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);
+ s->write_chr = val;
+ s->write_single_retries = 0;
+ serial_chr_write(s);
}
break;
case 1:
@@ -245,9 +351,11 @@ static uint32_t serial_ioport_read(void *opaque, uint32_t addr)
ret = s->mcr;
break;
case 5:
+ serial_get_token();
ret = s->lsr;
break;
case 6:
+ serial_get_token();
if (s->mcr & UART_MCR_LOOP) {
/* in loopback, the modem output pins are connected to the
inputs */
@@ -296,12 +404,14 @@ static int serial_can_receive1(void *opaque)
static void serial_receive1(void *opaque, const uint8_t *buf, int size)
{
SerialState *s = opaque;
+ tokens_avail = TOKENS_MAX;
serial_receive_byte(s, buf[0]);
}
static void serial_event(void *opaque, int event)
{
SerialState *s = opaque;
+ tokens_avail = TOKENS_MAX;
if (event == CHR_EVENT_BREAK)
serial_receive_break(s);
}
@@ -356,6 +466,7 @@ SerialState *serial_init(SetIRQFunc *set_irq, void *opaque,
s->lsr = UART_LSR_TEMT | UART_LSR_THRE;
s->iir = UART_IIR_NO_INT;
s->msr = UART_MSR_DCD | UART_MSR_DSR | UART_MSR_CTS;
+ s->write_retry_timer = qemu_new_timer(vm_clock, serial_chr_write, s);
register_savevm("serial", base, 1, serial_save, serial_load, s);
@@ -443,6 +554,7 @@ SerialState *serial_mm_init (SetIRQFunc *set_irq, void *opaque,
s->msr = UART_MSR_DCD | UART_MSR_DSR | UART_MSR_CTS;
s->base = base;
s->it_shift = it_shift;
+ s->write_retry_timer = qemu_new_timer(vm_clock, serial_chr_write, s);
register_savevm("serial", base, 1, serial_save, serial_load, s);