Parse command-line options much earlier, so that thread_mlfqs is
[pintos-anon] / src / devices / serial.c
index 8d1633f351b241b731ec8f8dcc1127fbba4b078b..f64074a0d1482bec6654b34b8e5b5644c8a76fec 100644 (file)
-#include "serial.h"
-#include "16550a.h"
-#include "debug.h"
-#include "io.h"
-#include "timer.h"
+#include "devices/serial.h"
+#include <debug.h>
+#include "devices/input.h"
+#include "devices/intq.h"
+#include "devices/timer.h"
+#include "threads/io.h"
+#include "threads/interrupt.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+\f
+/* Register definitions for the 16550A UART used in PCs.
+   The 16550A has a lot more going on than shown here, but this
+   is all we need.
 
+   Refer to [PC16650D] for hardware information. */
+
+/* I/O port base address for the first serial port. */
+#define IO_BASE 0x3f8
+
+/* 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.. */
+
+/* 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. */
+#define LCR_N81 0x03            /* No parity, 8 data bits, 1 stop bit. */
+#define LCR_DLAB 0x80           /* Divisor Latch Access Bit (DLAB). */
+
+/* MODEM Control Register. */
+#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. */
+static enum { UNINIT, POLL, QUEUE } mode;
+
+/* Data to be transmitted. */
+static struct intq txq;
+
+static void set_serial (int bps);
+static void putc_poll (uint8_t);
+static void write_ier (void);
+static intr_handler_func serial_interrupt;
+
+/* Initializes the serial port device for polling mode.
+   Polling mode busy-waits for the serial port to become free
+   before writing to it.  It's slow, but until interrupts have
+   been initialized it's all we can do. */
+static void
+init_poll (void) 
+{
+  ASSERT (mode == UNINIT);
+  outb (IER_REG, 0);                    /* Turn off all interrupts. */
+  outb (FCR_REG, 0);                    /* Disable FIFO. */
+  set_serial (115200);                  /* 115.2 kbps, N-8-1. */
+  outb (MCR_REG, MCR_OUT2);             /* Required to enable interrupts. */
+  intq_init (&txq);
+  mode = POLL;
+} 
+
+/* Initializes the serial port device for queued interrupt-driven
+   I/O.  With interrupt-driven I/O we don't waste CPU time
+   waiting for the serial device to become ready. */
+void
+serial_init_queue (void) 
+{
+  enum intr_level old_level;
+
+  if (mode == UNINIT)
+    init_poll ();
+  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. */
+void
+serial_putc (uint8_t byte) 
+{
+  enum intr_level old_level = intr_disable ();
+
+  if (mode != QUEUE)
+    {
+      /* If we're not set up for interrupt-driven I/O yet,
+         use dumb polling to transmit a byte. */
+      if (mode == UNINIT)
+        init_poll ();
+      putc_poll (byte); 
+    }
+  else 
+    {
+      /* Otherwise, queue a byte and update the interrupt enable
+         register. */
+      if (old_level == INTR_OFF && intq_full (&txq)) 
+        {
+          /* Interrupts are off and the transmit queue is full.
+             If we wanted to wait for the queue to empty,
+             we'd have to reenable interrupts.
+             That's impolite, so we'll send a character via
+             polling instead. */
+          putc_poll (intq_getc (&txq)); 
+        }
+
+      intq_putc (&txq, byte); 
+      write_ier ();
+    }
+  
+  intr_set_level (old_level);
+}
+
+/* Flushes anything in the serial buffer out the port in polling
+   mode. */
+void
+serial_flush (void) 
+{
+  enum intr_level old_level = intr_disable ();
+  while (!intq_empty (&txq))
+    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 bits, enum parity_type parity, int stop)
+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, make_lcr (bits, parity, stop, false, true));
+  outb (LCR_REG, LCR_N81 | LCR_DLAB);
 
-  /* Set baud rate. */
+  /* Set data rate. */
   outb (LS_REG, divisor & 0xff);
   outb (MS_REG, divisor >> 8);
   
   /* Reset DLAB. */
-  outb (LCR_REG, make_lcr (bits, parity, stop, false, false));
+  outb (LCR_REG, LCR_N81);
 }
 
-void
-serial_init (void) 
+/* Update interrupt enable register. */
+static void
+write_ier (void) 
 {
-  outb (IER_REG, 0);    /* Turn off all interrupts. */
-  outb (FCR_REG, 0);    /* Disable FIFO. */
-  set_serial (9600, 8, NONE, 1);
-  outb (MCR_REG, 0);    /* Turn off output lines. */
+  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);
 }
 
-void
-serial_outb (uint8_t byte) 
+/* Polls the serial port until it's ready,
+   and then transmits BYTE. */
+static void
+putc_poll (uint8_t byte) 
 {
+  ASSERT (intr_get_level () == INTR_OFF);
+
   while ((inb (LSR_REG) & LSR_THRE) == 0)
     continue;
   outb (THR_REG, byte);
 }
+
+/* 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 ();
+}