Merge 16550a.h into serial.c.
[pintos-anon] / src / devices / serial.c
1 #include "devices/serial.h"
2 #include <debug.h>
3 #include "devices/intq.h"
4 #include "devices/timer.h"
5 #include "threads/io.h"
6 #include "threads/interrupt.h"
7 #include "threads/synch.h"
8 #include "threads/thread.h"
9 \f
10 /* Register definitions for the 16550A UART used in PCs.
11    The 16550A has a lot more going on than shown here, but this
12    is all we need. */
13
14 /* I/O port base address for the first serial port. */
15 #define IO_BASE 0x3f8
16
17 /* DLAB=0 registers. */
18 #define RBR_REG (IO_BASE + 0)   /* Receiver Buffer Reg. (read-only). */
19 #define THR_REG (IO_BASE + 0)   /* Transmitter Holding Reg. (write-only). */
20 #define IER_REG (IO_BASE + 1)   /* Interrupt Enable Reg. (read-only). */
21 #define FCR_REG (IO_BASE + 2)   /* FIFO Control Reg. (write-only). */
22 #define LCR_REG (IO_BASE + 3)   /* Line Control Register. */
23 #define MCR_REG (IO_BASE + 4)   /* MODEM Control Register. */
24 #define LSR_REG (IO_BASE + 5)   /* Line Status Register (read-only). */
25
26 /* DLAB=1 registers. */
27 #define LS_REG (IO_BASE + 0)    /* Divisor Latch (LSB). */
28 #define MS_REG (IO_BASE + 1)    /* Divisor Latch (MSB). */
29
30 /* Interrupt Enable Register bits. */
31 #define IER_XMIT 0x02           /* Interrupt when transmit finishes. */
32
33 /* Line Control Register bits. */
34 #define LCR_N81 0x03            /* No parity, 8 data bits, 1 stop bit. */
35 #define LCR_DLAB 0x80           /* Divisor Latch Access Bit (DLAB). */
36
37 /* MODEM Control Register. */
38 #define MCR_OUT2 0x08           /* Output line 2. */
39
40 /* Line Status Register. */
41 #define LSR_THRE 0x20           /* THR Empty. */
42 \f
43 /* Transmission mode. */
44 static enum { UNINIT, POLL, QUEUE } mode;
45
46 /* Data to be transmitted. */
47 static struct intq txq;
48
49 static void set_serial (int bps);
50 static void putc_poll (uint8_t);
51 static void write_ier (void);
52 static intr_handler_func serial_interrupt;
53
54 /* Initializes the serial port device for polling mode.
55    Polling mode busy-waits for the serial port to become free
56    before writing to it.  It's slow, but until interrupts have
57    been initialized it's all we can do. */
58 void
59 serial_init_poll (void) 
60 {
61   ASSERT (mode == UNINIT);
62   outb (IER_REG, 0);                    /* Turn off all interrupts. */
63   outb (FCR_REG, 0);                    /* Disable FIFO. */
64   set_serial (9600);                    /* 9600 bps, N-8-1. */
65   outb (MCR_REG, MCR_OUT2);             /* Turn on OUT2 output line. */
66   intq_init (&txq, "serial xmit");
67   mode = POLL;
68
69
70 /* Initializes the serial port device for queued interrupt-driven
71    I/O.  With interrupt-driven I/O we don't waste CPU time
72    waiting for the serial device to become ready. */
73 void
74 serial_init_queue (void) 
75 {
76   ASSERT (mode == POLL);
77   intr_register (0x20 + 4, 0, INTR_OFF, serial_interrupt, "serial");
78   mode = QUEUE;
79 }
80
81 /* Sends BYTE to the serial port. */
82 void
83 serial_putc (uint8_t byte) 
84 {
85   enum intr_level old_level = intr_disable ();
86
87   if (mode == POLL)
88     {
89       /* If we're not set up for interrupt-driven I/O yet,
90          use dumb polling to transmit a byte. */
91       putc_poll (byte); 
92     }
93   else 
94     {
95       /* Otherwise, queue a byte and update the interrupt enable
96          register. */
97       if (old_level == INTR_OFF && intq_full (&txq)) 
98         {
99           /* Interrupts are off and the transmit queue is full.
100              If we wanted to wait for the queue to empty,
101              we'd have to reenable interrupts.
102              That's impolite, so we'll send a character via
103              polling instead. */
104           putc_poll (intq_getc (&txq)); 
105         }
106
107       intq_putc (&txq, byte); 
108       write_ier ();
109     }
110   
111   intr_set_level (old_level);
112 }
113
114 /* Flushes anything in the serial buffer out the port in polling
115    mode. */
116 void
117 serial_flush (void) 
118 {
119   enum intr_level old_level = intr_disable ();
120   while (!intq_empty (&txq))
121     putc_poll (intq_getc (&txq));
122   intr_set_level (old_level);
123 }
124 \f
125 /* Configures the serial port for BPS bits per second. */
126 static void
127 set_serial (int bps)
128 {
129   int baud_base = 1843200 / 16;         /* Base rate of 16550A. */
130   uint16_t divisor = baud_base / bps;   /* Clock rate divisor. */
131
132   /* Enable DLAB. */
133   outb (LCR_REG, LCR_N81 | LCR_DLAB);
134
135   /* Set baud rate. */
136   outb (LS_REG, divisor & 0xff);
137   outb (MS_REG, divisor >> 8);
138   
139   /* Reset DLAB. */
140   outb (LCR_REG, LCR_N81);
141 }
142
143 /* Update interrupt enable register.
144    If our transmit queue is empty, turn off transmit interrupt. */
145 static void
146 write_ier (void) 
147 {
148   outb (IER_REG, intq_empty (&txq) ? 0 : IER_XMIT);
149 }
150
151 /* Polls the serial port until it's ready,
152    and then transmits BYTE. */
153 static void
154 putc_poll (uint8_t byte) 
155 {
156   ASSERT (intr_get_level () == INTR_OFF);
157
158   while ((inb (LSR_REG) & LSR_THRE) == 0)
159     continue;
160   outb (THR_REG, byte);
161 }
162
163 /* Serial interrupt handler.
164    As long as we have a byte to transmit,
165    and the hardware is ready to accept a byte for transmission,
166    transmit a byte.
167    Then update interrupt enable register based on queue
168    status. */
169 static void
170 serial_interrupt (struct intr_frame *f UNUSED) 
171 {
172   while (!intq_empty (&txq) && (inb (LSR_REG) & LSR_THRE) != 0) 
173     outb (THR_REG, intq_getc (&txq));
174   write_ier ();
175 }