PKUOS - Pintos
Pintos source browser for PKU Operating System course
serial.c
Go to the documentation of this file.
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. */
52static enum { UNINIT, POLL, QUEUE } mode;
53
54/** Data to be transmitted. */
55static struct intq txq;
56
57static void set_serial (int bps);
58static void putc_poll (uint8_t);
59static void write_ier (void);
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. */
66static void
67init_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. */
81void
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. */
98void
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. */
134void
136{
137 enum intr_level old_level = intr_disable ();
138 while (!intq_empty (&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. */
147void
149{
151 if (mode == QUEUE)
152 write_ier ();
153}
154
155/** Configures the serial port for BPS bits per second. */
156static void
157set_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. */
166
167 /* Set data rate. */
168 outb (LS_REG, divisor & 0xff);
169 outb (MS_REG, divisor >> 8);
170
171 /* Reset DLAB. */
173}
174
175/** Update interrupt enable register. */
176static void
178{
179 uint8_t ier = 0;
180
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. */
198static void
200{
202
203 while ((inb (LSR_REG) & LSR_THRE) == 0)
204 continue;
205 outb (THR_REG, byte);
206}
207
208/** Serial interrupt handler. */
209static void
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)
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)
225
226 /* Update interrupt enable register based on queue status. */
227 write_ier ();
228}
#define ASSERT(CONDITION)
This is outside the header guard so that debug.h may be included multiple times with different settin...
Definition: debug.h:31
#define UNUSED
GCC lets us add "attributes" to functions, function parameters, etc.
Definition: debug.h:7
bool input_full(void)
Returns true if the input buffer is full, false otherwise.
Definition: input.c:48
void input_putc(uint8_t key)
Adds a key to the input buffer.
Definition: input.c:19
void intr_register_ext(uint8_t vec_no, intr_handler_func *handler, const char *name)
Registers external interrupt VEC_NO to invoke HANDLER, which is named NAME for debugging purposes.
Definition: interrupt.c:181
enum intr_level intr_disable(void)
Disables interrupts and returns the previous interrupt status.
Definition: interrupt.c:104
enum intr_level intr_get_level(void)
Returns the current interrupt status.
Definition: interrupt.c:65
enum intr_level intr_set_level(enum intr_level level)
Enables or disables interrupts as specified by LEVEL and returns the previous interrupt status.
Definition: interrupt.c:81
void intr_handler_func(struct intr_frame *)
Definition: interrupt.h:58
intr_level
Interrupts on or off?
Definition: interrupt.h:9
@ INTR_OFF
Interrupts disabled.
Definition: interrupt.h:10
bool intq_empty(const struct intq *q)
Returns true if Q is empty, false otherwise.
Definition: intq.c:20
uint8_t intq_getc(struct intq *q)
Removes a byte from Q and returns it.
Definition: intq.c:38
void intq_putc(struct intq *q, uint8_t byte)
Adds BYTE to the end of Q.
Definition: intq.c:61
void intq_init(struct intq *q)
Initializes interrupt queue Q.
Definition: intq.c:11
bool intq_full(const struct intq *q)
Returns true if Q is full, false otherwise.
Definition: intq.c:28
static uint8_t inb(uint16_t port)
Reads and returns a byte from PORT.
Definition: io.h:9
static void outb(uint16_t port, uint8_t data)
Writes byte DATA to PORT.
Definition: io.h:66
void serial_flush(void)
Flushes anything in the serial buffer out the port in polling mode.
Definition: serial.c:135
@ QUEUE
Definition: serial.c:52
@ UNINIT
Definition: serial.c:52
@ POLL
Definition: serial.c:52
void serial_putc(uint8_t byte)
Sends BYTE to the serial port.
Definition: serial.c:99
static void putc_poll(uint8_t)
Polls the serial port until it's ready, and then transmits BYTE.
Definition: serial.c:199
#define LCR_DLAB
Divisor Latch Access Bit (DLAB).
Definition: serial.c:42
static intr_handler_func serial_interrupt
Definition: serial.c:60
#define MCR_OUT2
MODEM Control Register.
Definition: serial.c:45
#define RBR_REG
DLAB=0 registers.
Definition: serial.c:21
#define MS_REG
Divisor Latch (MSB).
Definition: serial.c:27
static struct intq txq
Data to be transmitted.
Definition: serial.c:55
#define LSR_REG
Line Status Register (read-only).
Definition: serial.c:34
void serial_init_queue(void)
Initializes the serial port device for queued interrupt-driven I/O.
Definition: serial.c:82
#define THR_REG
Transmitter Holding Reg.
Definition: serial.c:22
#define IER_XMIT
Interrupt when transmit finishes.
Definition: serial.c:38
static enum @0 mode
Transmission mode.
#define LCR_REG
Line Control Register.
Definition: serial.c:32
#define IIR_REG
DLAB-insensitive registers.
Definition: serial.c:30
#define LSR_THRE
THR Empty.
Definition: serial.c:49
static void init_poll(void)
Initializes the serial port device for polling mode.
Definition: serial.c:67
#define FCR_REG
FIFO Control Reg.
Definition: serial.c:31
#define LCR_N81
Line Control Register bits.
Definition: serial.c:41
#define LS_REG
DLAB=1 registers.
Definition: serial.c:26
static void write_ier(void)
Update interrupt enable register.
Definition: serial.c:177
#define IER_REG
Interrupt Enable Reg.
Definition: serial.c:23
#define MCR_REG
MODEM Control Register.
Definition: serial.c:33
void serial_notify(void)
The fullness of the input buffer may have changed.
Definition: serial.c:148
#define LSR_DR
Line Status Register.
Definition: serial.c:48
static void set_serial(int bps)
Configures the serial port for BPS bits per second.
Definition: serial.c:157
#define IER_RECV
Interrupt Enable Register bits.
Definition: serial.c:37
unsigned char uint8_t
Definition: stdint.h:20
unsigned short int uint16_t
Definition: stdint.h:23
A circular queue of bytes.
Definition: intq.h:25
Interrupt stack frame.
Definition: interrupt.h:21