diff options
Diffstat (limited to 'pintos-progos/devices/serial.c')
| -rw-r--r-- | pintos-progos/devices/serial.c | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/pintos-progos/devices/serial.c b/pintos-progos/devices/serial.c new file mode 100644 index 0000000..df770a7 --- /dev/null +++ b/pintos-progos/devices/serial.c | |||
| @@ -0,0 +1,228 @@ | |||
| 1 | #include "devices/serial.h" | ||
| 2 | #include <debug.h> | ||
| 3 | #include "devices/input.h" | ||
| 4 | #include "devices/intq.h" | ||
| 5 | #include "devices/timer.h" | ||
| 6 | #include "threads/io.h" | ||
| 7 | #include "threads/interrupt.h" | ||
| 8 | #include "threads/synch.h" | ||
| 9 | #include "threads/thread.h" | ||
| 10 | |||
| 11 | /* Register definitions for the 16550A UART used in PCs. | ||
| 12 | The 16550A has a lot more going on than shown here, but this | ||
| 13 | is all we need. | ||
| 14 | |||
| 15 | Refer to [PC16650D] for hardware information. */ | ||
| 16 | |||
| 17 | /* I/O port base address for the first serial port. */ | ||
| 18 | #define IO_BASE 0x3f8 | ||
| 19 | |||
| 20 | /* DLAB=0 registers. */ | ||
| 21 | #define RBR_REG (IO_BASE + 0) /* Receiver Buffer Reg. (read-only). */ | ||
| 22 | #define THR_REG (IO_BASE + 0) /* Transmitter Holding Reg. (write-only). */ | ||
| 23 | #define IER_REG (IO_BASE + 1) /* Interrupt Enable Reg.. */ | ||
| 24 | |||
| 25 | /* DLAB=1 registers. */ | ||
| 26 | #define LS_REG (IO_BASE + 0) /* Divisor Latch (LSB). */ | ||
| 27 | #define MS_REG (IO_BASE + 1) /* Divisor Latch (MSB). */ | ||
| 28 | |||
| 29 | /* DLAB-insensitive registers. */ | ||
| 30 | #define IIR_REG (IO_BASE + 2) /* Interrupt Identification Reg. (read-only) */ | ||
| 31 | #define FCR_REG (IO_BASE + 2) /* FIFO Control Reg. (write-only). */ | ||
| 32 | #define LCR_REG (IO_BASE + 3) /* Line Control Register. */ | ||
| 33 | #define MCR_REG (IO_BASE + 4) /* MODEM Control Register. */ | ||
| 34 | #define LSR_REG (IO_BASE + 5) /* Line Status Register (read-only). */ | ||
| 35 | |||
| 36 | /* Interrupt Enable Register bits. */ | ||
| 37 | #define IER_RECV 0x01 /* Interrupt when data received. */ | ||
| 38 | #define IER_XMIT 0x02 /* Interrupt when transmit finishes. */ | ||
| 39 | |||
| 40 | /* Line Control Register bits. */ | ||
| 41 | #define LCR_N81 0x03 /* No parity, 8 data bits, 1 stop bit. */ | ||
| 42 | #define LCR_DLAB 0x80 /* Divisor Latch Access Bit (DLAB). */ | ||
| 43 | |||
| 44 | /* MODEM Control Register. */ | ||
| 45 | #define MCR_OUT2 0x08 /* Output line 2. */ | ||
| 46 | |||
| 47 | /* Line Status Register. */ | ||
| 48 | #define LSR_DR 0x01 /* Data Ready: received data byte is in RBR. */ | ||
| 49 | #define LSR_THRE 0x20 /* THR Empty. */ | ||
| 50 | |||
| 51 | /* Transmission mode. */ | ||
| 52 | static enum { UNINIT, POLL, QUEUE } mode; | ||
| 53 | |||
| 54 | /* Data to be transmitted. */ | ||
| 55 | static struct intq txq; | ||
| 56 | |||
| 57 | static void set_serial (int bps); | ||
| 58 | static void putc_poll (uint8_t); | ||
| 59 | static void write_ier (void); | ||
| 60 | static intr_handler_func serial_interrupt; | ||
| 61 | |||
| 62 | /* Initializes the serial port device for polling mode. | ||
| 63 | Polling mode busy-waits for the serial port to become free | ||
| 64 | before writing to it. It's slow, but until interrupts have | ||
| 65 | been initialized it's all we can do. */ | ||
| 66 | static void | ||
| 67 | init_poll (void) | ||
| 68 | { | ||
| 69 | ASSERT (mode == UNINIT); | ||
| 70 | outb (IER_REG, 0); /* Turn off all interrupts. */ | ||
| 71 | outb (FCR_REG, 0); /* Disable FIFO. */ | ||
| 72 | set_serial (9600); /* 9.6 kbps, N-8-1. */ | ||
| 73 | outb (MCR_REG, MCR_OUT2); /* Required to enable interrupts. */ | ||
| 74 | intq_init (&txq); | ||
| 75 | mode = POLL; | ||
| 76 | } | ||
| 77 | |||
| 78 | /* Initializes the serial port device for queued interrupt-driven | ||
| 79 | I/O. With interrupt-driven I/O we don't waste CPU time | ||
| 80 | waiting for the serial device to become ready. */ | ||
| 81 | void | ||
| 82 | serial_init_queue (void) | ||
| 83 | { | ||
| 84 | enum intr_level old_level; | ||
| 85 | |||
| 86 | if (mode == UNINIT) | ||
| 87 | init_poll (); | ||
| 88 | ASSERT (mode == POLL); | ||
| 89 | |||
| 90 | intr_register_ext (0x20 + 4, serial_interrupt, "serial"); | ||
| 91 | mode = QUEUE; | ||
| 92 | old_level = intr_disable (); | ||
| 93 | write_ier (); | ||
| 94 | intr_set_level (old_level); | ||
| 95 | } | ||
| 96 | |||
| 97 | /* Sends BYTE to the serial port. */ | ||
| 98 | void | ||
| 99 | serial_putc (uint8_t byte) | ||
| 100 | { | ||
| 101 | enum intr_level old_level = intr_disable (); | ||
| 102 | |||
| 103 | if (mode != QUEUE) | ||
| 104 | { | ||
| 105 | /* If we're not set up for interrupt-driven I/O yet, | ||
| 106 | use dumb polling to transmit a byte. */ | ||
| 107 | if (mode == UNINIT) | ||
| 108 | init_poll (); | ||
| 109 | putc_poll (byte); | ||
| 110 | } | ||
| 111 | else | ||
| 112 | { | ||
| 113 | /* Otherwise, queue a byte and update the interrupt enable | ||
| 114 | register. */ | ||
| 115 | if (old_level == INTR_OFF && intq_full (&txq)) | ||
| 116 | { | ||
| 117 | /* Interrupts are off and the transmit queue is full. | ||
| 118 | If we wanted to wait for the queue to empty, | ||
| 119 | we'd have to reenable interrupts. | ||
| 120 | That's impolite, so we'll send a character via | ||
| 121 | polling instead. */ | ||
| 122 | putc_poll (intq_getc (&txq)); | ||
| 123 | } | ||
| 124 | |||
| 125 | intq_putc (&txq, byte); | ||
| 126 | write_ier (); | ||
| 127 | } | ||
| 128 | |||
| 129 | intr_set_level (old_level); | ||
| 130 | } | ||
| 131 | |||
| 132 | /* Flushes anything in the serial buffer out the port in polling | ||
| 133 | mode. */ | ||
| 134 | void | ||
| 135 | serial_flush (void) | ||
| 136 | { | ||
| 137 | enum intr_level old_level = intr_disable (); | ||
| 138 | while (!intq_empty (&txq)) | ||
| 139 | putc_poll (intq_getc (&txq)); | ||
| 140 | intr_set_level (old_level); | ||
| 141 | } | ||
| 142 | |||
| 143 | /* The fullness of the input buffer may have changed. Reassess | ||
| 144 | whether we should block receive interrupts. | ||
| 145 | Called by the input buffer routines when characters are added | ||
| 146 | to or removed from the buffer. */ | ||
| 147 | void | ||
| 148 | serial_notify (void) | ||
| 149 | { | ||
| 150 | ASSERT (intr_get_level () == INTR_OFF); | ||
| 151 | if (mode == QUEUE) | ||
| 152 | write_ier (); | ||
| 153 | } | ||
| 154 | |||
| 155 | /* Configures the serial port for BPS bits per second. */ | ||
| 156 | static void | ||
| 157 | set_serial (int bps) | ||
| 158 | { | ||
| 159 | int base_rate = 1843200 / 16; /* Base rate of 16550A, in Hz. */ | ||
| 160 | uint16_t divisor = base_rate / bps; /* Clock rate divisor. */ | ||
| 161 | |||
| 162 | ASSERT (bps >= 300 && bps <= 115200); | ||
| 163 | |||
| 164 | /* Enable DLAB. */ | ||
| 165 | outb (LCR_REG, LCR_N81 | LCR_DLAB); | ||
| 166 | |||
| 167 | /* Set data rate. */ | ||
| 168 | outb (LS_REG, divisor & 0xff); | ||
| 169 | outb (MS_REG, divisor >> 8); | ||
| 170 | |||
| 171 | /* Reset DLAB. */ | ||
| 172 | outb (LCR_REG, LCR_N81); | ||
| 173 | } | ||
| 174 | |||
| 175 | /* Update interrupt enable register. */ | ||
| 176 | static void | ||
| 177 | write_ier (void) | ||
| 178 | { | ||
| 179 | uint8_t ier = 0; | ||
| 180 | |||
| 181 | ASSERT (intr_get_level () == INTR_OFF); | ||
| 182 | |||
| 183 | /* Enable transmit interrupt if we have any characters to | ||
| 184 | transmit. */ | ||
| 185 | if (!intq_empty (&txq)) | ||
| 186 | ier |= IER_XMIT; | ||
| 187 | |||
| 188 | /* Enable receive interrupt if we have room to store any | ||
| 189 | characters we receive. */ | ||
| 190 | if (!input_full ()) | ||
| 191 | ier |= IER_RECV; | ||
| 192 | |||
| 193 | outb (IER_REG, ier); | ||
| 194 | } | ||
| 195 | |||
| 196 | /* Polls the serial port until it's ready, | ||
| 197 | and then transmits BYTE. */ | ||
| 198 | static void | ||
| 199 | putc_poll (uint8_t byte) | ||
| 200 | { | ||
| 201 | ASSERT (intr_get_level () == INTR_OFF); | ||
| 202 | |||
| 203 | while ((inb (LSR_REG) & LSR_THRE) == 0) | ||
| 204 | continue; | ||
| 205 | outb (THR_REG, byte); | ||
| 206 | } | ||
| 207 | |||
| 208 | /* Serial interrupt handler. */ | ||
| 209 | static void | ||
| 210 | serial_interrupt (struct intr_frame *f UNUSED) | ||
| 211 | { | ||
| 212 | /* Inquire about interrupt in UART. Without this, we can | ||
| 213 | occasionally miss an interrupt running under QEMU. */ | ||
| 214 | inb (IIR_REG); | ||
| 215 | |||
| 216 | /* As long as we have room to receive a byte, and the hardware | ||
| 217 | has a byte for us, receive a byte. */ | ||
| 218 | while (!input_full () && (inb (LSR_REG) & LSR_DR) != 0) | ||
| 219 | input_putc (inb (RBR_REG)); | ||
| 220 | |||
| 221 | /* As long as we have a byte to transmit, and the hardware is | ||
| 222 | ready to accept a byte for transmission, transmit a byte. */ | ||
| 223 | while (!intq_empty (&txq) && (inb (LSR_REG) & LSR_THRE) != 0) | ||
| 224 | outb (THR_REG, intq_getc (&txq)); | ||
| 225 | |||
| 226 | /* Update interrupt enable register based on queue status. */ | ||
| 227 | write_ier (); | ||
| 228 | } | ||
