# HG changeset patch
# User Steven Smith <ssmith@xensource.com>
# Node ID 1d3f52eb256e3522edc12daca91039b319dbbbe5
# Parent  b7b653e36d20811831f26bb951ea66dca5854b17
[HVM] Rate limit guest accesses to the qemu virtual serial port.  This stops
grub's boot menu from hammering dom0.

Signed-off-by: Steven Smith <sos22@cam.ac.uk>

--- ioemu/hw/serial.c	Mon Sep 25 16:31:02 2006 +0100
+++ ioemu/hw/serial.c	Mon Sep 25 17:27:18 2006 +0100
@@ -22,6 +22,9 @@
  * THE SOFTWARE.
  */
 #include "vl.h"
+#include <sys/time.h>
+#include <time.h>
+#include <assert.h>
 
 //#define DEBUG_SERIAL
 
@@ -138,6 +141,67 @@ static void serial_update_parameters(Ser
     printf("speed=%d parity=%c data=%d stop=%d\n", 
            speed, parity, data_bits, stop_bits);
 #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_ioport_write(void *opaque, uint32_t addr, uint32_t val)
@@ -245,9 +309,11 @@ static uint32_t serial_ioport_read(void 
         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 +362,14 @@ static void serial_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);
 }
