X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Fdevices%2Fserial.c;h=df770a724ac95c6825ed8ba7793bf99a1612c5df;hb=43dadfe13b843be54ac5fdc9e4496beb47af3b1f;hp=8dc7f18cfbd0272b630e0d85c87abf119be23173;hpb=34faba4d412c8d856d8d26dc9d82c0cf5b906d3c;p=pintos-anon diff --git a/src/devices/serial.c b/src/devices/serial.c index 8dc7f18..df770a7 100644 --- a/src/devices/serial.c +++ b/src/devices/serial.c @@ -1,5 +1,6 @@ #include "devices/serial.h" #include +#include "devices/input.h" #include "devices/intq.h" #include "devices/timer.h" #include "threads/io.h" @@ -19,17 +20,21 @@ /* 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. */ /* Transmission mode. */ @@ -57,15 +63,15 @@ static intr_handler_func serial_interrupt; 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. */ -void -serial_init_poll (void) +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); /* 9600 bps, N-8-1. */ - outb (MCR_REG, MCR_OUT2); /* Turn on OUT2 output line. */ - intq_init (&txq, "serial xmit"); + set_serial (9600); /* 9.6 kbps, N-8-1. */ + outb (MCR_REG, MCR_OUT2); /* Required to enable interrupts. */ + intq_init (&txq); mode = POLL; } @@ -75,9 +81,17 @@ serial_init_poll (void) void serial_init_queue (void) { + enum intr_level old_level; + + if (mode == UNINIT) + init_poll (); 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. */ @@ -86,10 +100,12 @@ serial_putc (uint8_t byte) { enum intr_level old_level = intr_disable (); - if (mode == POLL) + 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 @@ -123,18 +139,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 (); +} /* 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); @@ -142,12 +172,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, @@ -162,16 +205,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 (); }