CS318 - Pintos
Pintos source browser for JHU CS318 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. */
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);
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
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
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
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
149 {
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. */
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 
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
200 {
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
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 }
set_serial
static void set_serial(int bps)
Configures the serial port for BPS bits per second.
Definition: serial.c:157
LSR_DR
#define LSR_DR
Line Status Register.
Definition: serial.c:48
IIR_REG
#define IIR_REG
DLAB-insensitive registers.
Definition: serial.c:30
uint8_t
unsigned char uint8_t
Definition: stdint.h:20
LCR_N81
#define LCR_N81
Line Control Register bits.
Definition: serial.c:41
LCR_REG
#define LCR_REG
Line Control Register.
Definition: serial.c:32
QUEUE
Definition: serial.c:52
UNINIT
Definition: serial.c:52
IER_XMIT
#define IER_XMIT
Interrupt when transmit finishes.
Definition: serial.c:38
mode
static enum @0 mode
Transmission mode.
intr_level
intr_level
Interrupts on or off?
Definition: interrupt.h:8
LCR_DLAB
#define LCR_DLAB
Divisor Latch Access Bit (DLAB).
Definition: serial.c:42
intq_empty
bool intq_empty(const struct intq *q)
Returns true if Q is empty, false otherwise.
Definition: intq.c:20
serial_interrupt
static intr_handler_func serial_interrupt
Definition: serial.c:60
IER_REG
#define IER_REG
Interrupt Enable Reg.
Definition: serial.c:23
intr_set_level
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
UNUSED
#define UNUSED
GCC lets us add "attributes" to functions, function parameters, etc.
Definition: debug.h:7
THR_REG
#define THR_REG
Transmitter Holding Reg.
Definition: serial.c:22
uint16_t
unsigned short int uint16_t
Definition: stdint.h:23
init_poll
static void init_poll(void)
Initializes the serial port device for polling mode.
Definition: serial.c:67
MCR_REG
#define MCR_REG
MODEM Control Register.
Definition: serial.c:33
intq_init
void intq_init(struct intq *q)
Initializes interrupt queue Q.
Definition: intq.c:11
intq_getc
uint8_t intq_getc(struct intq *q)
Removes a byte from Q and returns it.
Definition: intq.c:38
LSR_THRE
#define LSR_THRE
THR Empty.
Definition: serial.c:49
intq.h
input_full
bool input_full(void)
Returns true if the input buffer is full, false otherwise.
Definition: input.c:48
interrupt.h
intr_get_level
enum intr_level intr_get_level(void)
Returns the current interrupt status.
Definition: interrupt.c:65
timer.h
MS_REG
#define MS_REG
Divisor Latch (MSB).
Definition: serial.c:27
LS_REG
#define LS_REG
DLAB=1 registers.
Definition: serial.c:26
RBR_REG
#define RBR_REG
DLAB=0 registers.
Definition: serial.c:21
intq_putc
void intq_putc(struct intq *q, uint8_t byte)
Adds BYTE to the end of Q.
Definition: intq.c:61
serial_notify
void serial_notify(void)
The fullness of the input buffer may have changed.
Definition: serial.c:148
intq_full
bool intq_full(const struct intq *q)
Returns true if Q is full, false otherwise.
Definition: intq.c:28
LSR_REG
#define LSR_REG
Line Status Register (read-only).
Definition: serial.c:34
intr_handler_func
void intr_handler_func(struct intr_frame *)
Definition: interrupt.h:58
putc_poll
static void putc_poll(uint8_t)
Polls the serial port until it's ready, and then transmits BYTE.
Definition: serial.c:199
intr_disable
enum intr_level intr_disable(void)
Disables interrupts and returns the previous interrupt status.
Definition: interrupt.c:104
ASSERT
#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
write_ier
static void write_ier(void)
Update interrupt enable register.
Definition: serial.c:177
POLL
Definition: serial.c:52
outb
static void outb(uint16_t port, uint8_t data)
Writes byte DATA to PORT.
Definition: io.h:66
serial.h
intq
A circular queue of bytes.
Definition: intq.h:24
inb
static uint8_t inb(uint16_t port)
Reads and returns a byte from PORT.
Definition: io.h:9
io.h
IER_RECV
#define IER_RECV
Interrupt Enable Register bits.
Definition: serial.c:37
FCR_REG
#define FCR_REG
FIFO Control Reg.
Definition: serial.c:31
INTR_OFF
Interrupts disabled.
Definition: interrupt.h:10
serial_putc
void serial_putc(uint8_t byte)
Sends BYTE to the serial port.
Definition: serial.c:99
MCR_OUT2
#define MCR_OUT2
MODEM Control Register.
Definition: serial.c:45
serial_init_queue
void serial_init_queue(void)
Initializes the serial port device for queued interrupt-driven I/O.
Definition: serial.c:82
serial_flush
void serial_flush(void)
Flushes anything in the serial buffer out the port in polling mode.
Definition: serial.c:135
txq
static struct intq txq
Data to be transmitted.
Definition: serial.c:55
intr_register_ext
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
intr_frame
Interrupt stack frame.
Definition: interrupt.h:20
input_putc
void input_putc(uint8_t key)
Adds a key to the input buffer.
Definition: input.c:19
thread.h
synch.h
input.h
debug.h