#include "devices/serial.h"
#include <debug.h>
-#include "devices/16550a.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.
-#define QUEUE_BUFSIZE 8
+ Refer to [PC16650D] for hardware information. */
-struct queue
- {
- enum intr_level old_level;
- struct lock lock;
-
- struct thread *not_full, *not_empty;
- uint8_t buf[QUEUE_BUFSIZE];
- int head, tail;
- };
+/* I/O port base address for the first serial port. */
+#define IO_BASE 0x3f8
-static struct queue recv_queue, xmit_queue;
-static bool polled_io;
+/* 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.. */
-static void set_serial (int bps, int bits, enum parity_type parity, int stop);
-static void update_ier (void);
-static intr_handler_func serial_interrupt;
+/* 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. */
-static void queue_init (struct queue *, const char *);
-static void queue_lock (struct queue *);
-static void queue_unlock (struct queue *);
-static bool queue_empty (const struct queue *);
-static bool queue_full (const struct queue *);
-static uint8_t queue_getc (struct queue *);
-static void queue_putc (struct queue *, uint8_t);
+/* 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
void
serial_init_poll (void)
{
- ASSERT (!polled_io);
- polled_io = true;
+ ASSERT (mode == UNINIT);
outb (IER_REG, 0); /* Turn off all interrupts. */
outb (FCR_REG, 0); /* Disable FIFO. */
- set_serial (9600, 8, NONE, 1); /* 9600 bps, N-8-1. */
- outb (MCR_REG, 8); /* Turn on OUT2 output line. */
+ 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
void
serial_init_queue (void)
{
- ASSERT (polled_io);
- polled_io = false;
- queue_init (&recv_queue, "serial recv");
- queue_init (&xmit_queue, "serial xmit");
- intr_register (0x20 + 4, 0, INTR_OFF, serial_interrupt, "serial");
-}
+ enum intr_level old_level;
-static void putc_poll (uint8_t);
-static void putc_queue (uint8_t);
+ 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)
{
- if (polled_io || intr_context ())
- putc_poll (byte);
- else
- putc_queue (byte);
-}
+ enum intr_level old_level = intr_disable ();
-static void
-putc_poll (uint8_t byte)
-{
- while ((inb (LSR_REG) & LSR_THRE) == 0)
- continue;
- outb (THR_REG, byte);
+ if (mode == POLL)
+ {
+ /* If we're not set up for interrupt-driven I/O yet,
+ use dumb polling to transmit a byte. */
+ 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);
}
-static void
-putc_queue (uint8_t byte)
+/* Flushes anything in the serial buffer out the port in polling
+ mode. */
+void
+serial_flush (void)
{
- queue_lock (&xmit_queue);
-
- queue_putc (&xmit_queue, byte);
- update_ier ();
-
- queue_unlock (&xmit_queue);
+ enum intr_level old_level = intr_disable ();
+ while (!intq_empty (&txq))
+ putc_poll (intq_getc (&txq));
+ intr_set_level (old_level);
}
-/* Reads and returns a byte from the serial port. */
-uint8_t
-serial_getc (void)
+/* 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)
{
- uint8_t byte;
- queue_lock (&recv_queue);
-
- /* Read a byte. */
- byte = queue_getc (&recv_queue);
- update_ier ();
-
- queue_unlock (&recv_queue);
- return byte;
+ ASSERT (intr_get_level () == INTR_OFF);
+ if (mode == QUEUE)
+ write_ier ();
}
-
-/* Configures the first serial port for BPS bits per second,
- BITS bits per byte, the given PARITY, and STOP stop bits. */
+\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);
}
-/* Update interrupt enable register.
- If our transmit queue is empty, turn off transmit interrupt.
- If our receive queue is full, turn off receive interrupt. */
+/* Update interrupt enable register. */
static void
-update_ier (void)
+write_ier (void)
{
uint8_t ier = 0;
-
+
ASSERT (intr_get_level () == INTR_OFF);
- if (!queue_empty (&xmit_queue))
+ /* Enable transmit interrupt if we have any characters to
+ transmit. */
+ if (!intq_empty (&txq))
ier |= IER_XMIT;
- if (!queue_full (&recv_queue))
- ier |= IER_RECV;
- outb (IER_REG, ier);
-}
-static void
-serial_interrupt (struct intr_frame *f UNUSED)
-{
- /* As long as we have space in our buffer for a byte,
- and the hardware has a byte to give us,
- receive a byte. */
- while (!queue_full (&recv_queue) && (inb (LSR_REG) & LSR_DR) != 0)
- {
- uint8_t byte = inb (RBR_REG);
- //outb (0x0402, byte);
- queue_putc (&recv_queue, byte);
- }
-
- /* As long as we have a byte to transmit,
- and the hardware is ready to accept a byte for transmission,
- transmit a byte. */
- while (!queue_empty (&xmit_queue) && (inb (LSR_REG) & LSR_THRE) != 0)
- outb (THR_REG, queue_getc (&xmit_queue));
+ /* Enable receive interrupt if we have room to store any
+ characters we receive. */
+ if (!input_full ())
+ ier |= IER_RECV;
- /* Update interrupt enable register based on queue status. */
- update_ier ();
-}
-\f
-static int next (int pos);
-static bool owned_by_current_thread (const struct queue *);
-static void wait (struct queue *q, struct thread **waiter);
-static void signal (struct queue *q, struct thread **waiter);
-
-static void
-queue_init (struct queue *q, const char *name)
-{
- lock_init (&q->lock, name);
- q->not_full = q->not_empty = NULL;
- q->head = q->tail = 0;
-}
-
-static void
-queue_lock (struct queue *q)
-{
- lock_acquire (&q->lock);
- q->old_level = intr_disable ();
+ outb (IER_REG, ier);
}
+/* Polls the serial port until it's ready,
+ and then transmits BYTE. */
static void
-queue_unlock (struct queue *q)
-{
- ASSERT (!intr_context ());
- ASSERT (owned_by_current_thread (q));
-
- lock_release (&q->lock);
- intr_set_level (q->old_level);
-}
-
-static bool
-queue_empty (const struct queue *q)
+putc_poll (uint8_t byte)
{
ASSERT (intr_get_level () == INTR_OFF);
- return q->head == q->tail;
-}
-
-static bool
-queue_full (const struct queue *q)
-{
- ASSERT (intr_get_level () == INTR_OFF);
- return next (q->head) == q->tail;
-}
-static uint8_t
-queue_getc (struct queue *q)
-{
- uint8_t byte;
-
- ASSERT (owned_by_current_thread (q));
- while (queue_empty (q))
- wait (q, &q->not_empty);
-
- byte = q->buf[q->tail];
- q->tail = next (q->tail);
- signal (q, &q->not_full);
- return byte;
-}
-
-static void
-queue_putc (struct queue *q, uint8_t byte)
-{
- ASSERT (owned_by_current_thread (q));
- while (queue_full (q))
- wait (q, &q->not_full);
-
- q->buf[q->head] = byte;
- q->head = next (q->head);
- signal (q, &q->not_empty);
-}
-
-static int
-next (int pos)
-{
- return (pos + 1) % QUEUE_BUFSIZE;
-}
-
-static bool
-owned_by_current_thread (const struct queue *q)
-{
- return (intr_context ()
- || (lock_held_by_current_thread (&q->lock)
- && intr_get_level () == INTR_OFF));
+ while ((inb (LSR_REG) & LSR_THRE) == 0)
+ continue;
+ outb (THR_REG, byte);
}
+/* Serial interrupt handler. */
static void
-wait (struct queue *q, struct thread **waiter)
+serial_interrupt (struct intr_frame *f UNUSED)
{
- ASSERT (!intr_context ());
- ASSERT (owned_by_current_thread (q));
+ /* Inquire about interrupt in UART. Without this, we can
+ occasionally miss an interrupt running under qemu. */
+ inb (IIR_REG);
- *waiter = thread_current ();
- thread_block ();
-}
+ /* 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));
-static void
-signal (struct queue *q, struct thread **waiter)
-{
- ASSERT (owned_by_current_thread (q));
+ /* 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));
- if (*waiter != NULL)
- {
- thread_unblock (*waiter);
- *waiter = NULL;
- }
+ /* Update interrupt enable register based on queue status. */
+ write_ier ();
}