229 lines
6.4 KiB
C
229 lines
6.4 KiB
C
|
#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"
|
|||
|
|
|||
|
/* 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. */
|
|||
|
|
|||
|
/* 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 (9600); /* 9.6 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 ();
|
|||
|
}
|
|||
|
|
|||
|
/* Configures the serial port for BPS bits per second. */
|
|||
|
static void
|
|||
|
set_serial (int bps)
|
|||
|
{
|
|||
|
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 data rate. */
|
|||
|
outb (LS_REG, divisor & 0xff);
|
|||
|
outb (MS_REG, divisor >> 8);
|
|||
|
|
|||
|
/* Reset DLAB. */
|
|||
|
outb (LCR_REG, LCR_N81);
|
|||
|
}
|
|||
|
|
|||
|
/* Update interrupt enable register. */
|
|||
|
static void
|
|||
|
write_ier (void)
|
|||
|
{
|
|||
|
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,
|
|||
|
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 ();
|
|||
|
}
|