#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. */
#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. */
ASSERT (mode == UNINIT);
outb (IER_REG, 0); /* Turn off all interrupts. */
outb (FCR_REG, 0); /* Disable FIFO. */
- set_serial (9600); /* 9600 bps, N-8-1. */
- outb (MCR_REG, MCR_OUT2); /* Turn on OUT2 output line. */
- intq_init (&txq, "serial xmit");
+ set_serial (115200); /* 115.2 kbps, N-8-1. */
+ outb (MCR_REG, MCR_OUT2); /* Required to enable interrupts. */
+ intq_init (&txq);
mode = POLL;
}
void
serial_init_queue (void)
{
+ enum intr_level old_level;
+
ASSERT (mode == POLL);
- intr_register (0x20 + 4, 0, INTR_OFF, serial_interrupt, "serial");
+
+ 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. */
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);
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,
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 ();
}