Add support for "keyboard" input over the serial port.
[pintos-anon] / src / devices / serial.c
index 1cb49a39ee25da6e35d4cd12356fb2a019cdd4b3..92b00ef06eaa67513be6858101f41358b54bb291 100644 (file)
@@ -1,5 +1,6 @@
 #include "devices/serial.h"
 #include <debug.h>
+#include "devices/input.h"
 #include "devices/intq.h"
 #include "devices/timer.h"
 #include "threads/io.h"
 /* DLAB=0 registers. */
 #define RBR_REG (IO_BASE + 0)   /* Receiver Buffer Reg. (read-only). */
 #define THR_REG (IO_BASE + 0)   /* Transmitter Holding Reg. (write-only). */
-#define IER_REG (IO_BASE + 1)   /* Interrupt Enable Reg. (read-only). */
-#define FCR_REG (IO_BASE + 2)   /* FIFO Control Reg. (write-only). */
-#define LCR_REG (IO_BASE + 3)   /* Line Control Register. */
-#define MCR_REG (IO_BASE + 4)   /* MODEM Control Register. */
-#define LSR_REG (IO_BASE + 5)   /* Line Status Register (read-only). */
+#define IER_REG (IO_BASE + 1)   /* Interrupt Enable Reg.. */
 
 /* DLAB=1 registers. */
 #define LS_REG (IO_BASE + 0)    /* Divisor Latch (LSB). */
 #define MS_REG (IO_BASE + 1)    /* Divisor Latch (MSB). */
 
+/* DLAB-insensitive registers. */
+#define IIR_REG (IO_BASE + 2)   /* Interrupt Identification Reg. (read-only) */
+#define FCR_REG (IO_BASE + 2)   /* FIFO Control Reg. (write-only). */
+#define LCR_REG (IO_BASE + 3)   /* Line Control Register. */
+#define MCR_REG (IO_BASE + 4)   /* MODEM Control Register. */
+#define LSR_REG (IO_BASE + 5)   /* Line Status Register (read-only). */
+
 /* Interrupt Enable Register bits. */
+#define IER_RECV 0x01           /* Interrupt when data received. */
 #define IER_XMIT 0x02           /* Interrupt when transmit finishes. */
 
 /* Line Control Register bits. */
@@ -40,6 +45,7 @@
 #define MCR_OUT2 0x08           /* Output line 2. */
 
 /* Line Status Register. */
+#define LSR_DR 0x01             /* Data Ready: received data byte is in RBR. */
 #define LSR_THRE 0x20           /* THR Empty. */
 \f
 /* Transmission mode. */
@@ -75,9 +81,15 @@ serial_init_poll (void)
 void
 serial_init_queue (void) 
 {
+  enum intr_level old_level;
+
   ASSERT (mode == POLL);
+
   intr_register_ext (0x20 + 4, serial_interrupt, "serial");
   mode = QUEUE;
+  old_level = intr_disable ();
+  write_ier ();
+  intr_set_level (old_level);
 }
 
 /* Sends BYTE to the serial port. */
@@ -123,20 +135,32 @@ serial_flush (void)
     putc_poll (intq_getc (&txq));
   intr_set_level (old_level);
 }
+
+/* The fullness of the input buffer may have changed.  Reassess
+   whether we should block receive interrupts.
+   Called by the input buffer routines when characters are added
+   to or removed from the buffer. */
+void
+serial_notify (void) 
+{
+  ASSERT (intr_get_level () == INTR_OFF);
+  if (mode == QUEUE)
+    write_ier ();
+}
 \f
 /* Configures the serial port for BPS bits per second. */
 static void
 set_serial (int bps)
 {
-  int baud_base = 1843200 / 16;         /* Base rate of 16550A. */
-  uint16_t divisor = baud_base / bps;   /* Clock rate divisor. */
+  int base_rate = 1843200 / 16;         /* Base rate of 16550A, in Hz. */
+  uint16_t divisor = base_rate / bps;   /* Clock rate divisor. */
 
   ASSERT (bps >= 300 && bps <= 115200);
 
   /* Enable DLAB. */
   outb (LCR_REG, LCR_N81 | LCR_DLAB);
 
-  /* Set baud rate. */
+  /* Set data rate. */
   outb (LS_REG, divisor & 0xff);
   outb (MS_REG, divisor >> 8);
   
@@ -144,12 +168,25 @@ set_serial (int bps)
   outb (LCR_REG, LCR_N81);
 }
 
-/* Update interrupt enable register.
-   If our transmit queue is empty, turn off transmit interrupt. */
+/* Update interrupt enable register. */
 static void
 write_ier (void) 
 {
-  outb (IER_REG, intq_empty (&txq) ? 0 : IER_XMIT);
+  uint8_t ier = 0;
+
+  ASSERT (intr_get_level () == INTR_OFF);
+
+  /* Enable transmit interrupt if we have any characters to
+     transmit. */
+  if (!intq_empty (&txq))
+    ier |= IER_XMIT;
+
+  /* Enable receive interrupt if we have room to store any
+     characters we receive. */
+  if (!input_full ())
+    ier |= IER_RECV;
+  
+  outb (IER_REG, ier);
 }
 
 /* Polls the serial port until it's ready,
@@ -164,16 +201,24 @@ putc_poll (uint8_t byte)
   outb (THR_REG, byte);
 }
 
-/* Serial interrupt handler.
-   As long as we have a byte to transmit,
-   and the hardware is ready to accept a byte for transmission,
-   transmit a byte.
-   Then update interrupt enable register based on queue
-   status. */
+/* Serial interrupt handler. */
 static void
 serial_interrupt (struct intr_frame *f UNUSED) 
 {
+  /* Inquire about interrupt in UART.  Without this, we can
+     occasionally miss an interrupt running under qemu. */
+  inb (IIR_REG);
+
+  /* As long as we have room to receive a byte, and the hardware
+     has a byte for us, receive a byte.  */
+  while (!input_full () && (inb (LSR_REG) & LSR_DR) != 0)
+    input_putc (inb (RBR_REG));
+
+  /* As long as we have a byte to transmit, and the hardware is
+     ready to accept a byte for transmission, transmit a byte. */
   while (!intq_empty (&txq) && (inb (LSR_REG) & LSR_THRE) != 0) 
     outb (THR_REG, intq_getc (&txq));
+
+  /* Update interrupt enable register based on queue status. */
   write_ier ();
 }