Really make it safe to call printf() from any context.
[pintos-anon] / src / devices / serial.c
1 #include "devices/serial.h"
2 #include <debug.h>
3 #include "devices/16550a.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 /* Transmission mode. */
12 static enum { UNINIT, POLL, QUEUE } mode;
13
14 /* Data to be transmitted. */
15 static struct intq txq;
16
17 static void set_serial (int bps, int bits, enum parity_type parity, int stop);
18 static void putc_poll (uint8_t);
19 static void write_ier (void);
20 static intr_handler_func serial_interrupt;
21
22 /* Initializes the serial port device for polling mode.
23    Polling mode busy-waits for the serial port to become free
24    before writing to it.  It's slow, but until interrupts have
25    been initialized it's all we can do. */
26 void
27 serial_init_poll (void) 
28 {
29   ASSERT (mode == UNINIT);
30   outb (IER_REG, 0);                    /* Turn off all interrupts. */
31   outb (FCR_REG, 0);                    /* Disable FIFO. */
32   set_serial (9600, 8, NONE, 1);        /* 9600 bps, N-8-1. */
33   outb (MCR_REG, 8);                    /* Turn on OUT2 output line. */
34   intq_init (&txq, "serial xmit");
35   mode = POLL;
36
37
38 /* Initializes the serial port device for queued interrupt-driven
39    I/O.  With interrupt-driven I/O we don't waste CPU time
40    waiting for the serial device to become ready. */
41 void
42 serial_init_queue (void) 
43 {
44   ASSERT (mode == POLL);
45   intr_register (0x20 + 4, 0, INTR_OFF, serial_interrupt, "serial");
46   mode = QUEUE;
47 }
48
49 /* Sends BYTE to the serial port. */
50 void
51 serial_putc (uint8_t byte) 
52 {
53   enum intr_level old_level = intr_disable ();
54
55   if (mode == POLL || old_level == INTR_OFF)
56     {
57       /* If we're not set up for interrupt-driven I/O yet,
58          or if interrupts are off,
59          use dumb polling for serial I/O. */
60       serial_flush ();
61       putc_poll (byte); 
62     }
63   else
64     {
65       /* Otherwise, queue a byte and update the interrupt enable
66          register. */
67       intq_putc (&txq, byte); 
68       write_ier ();
69     }
70   
71   intr_set_level (old_level);
72 }
73
74 /* Flushes anything in the serial buffer out the port in polling
75    mode. */
76 void
77 serial_flush (void) 
78 {
79   enum intr_level old_level = intr_disable ();
80   while (!intq_empty (&txq))
81     putc_poll (intq_getc (&txq));
82   intr_set_level (old_level);
83 }
84
85 /* Configures the serial port for BPS bits per second, BITS bits
86    per byte, the given PARITY, and STOP stop bits. */
87 static void
88 set_serial (int bps, int bits, enum parity_type parity, int stop)
89 {
90   int baud_base = 1843200 / 16;         /* Base rate of 16550A. */
91   uint16_t divisor = baud_base / bps;   /* Clock rate divisor. */
92
93   /* Enable DLAB. */
94   outb (LCR_REG, make_lcr (bits, parity, stop, false, true));
95
96   /* Set baud rate. */
97   outb (LS_REG, divisor & 0xff);
98   outb (MS_REG, divisor >> 8);
99   
100   /* Reset DLAB. */
101   outb (LCR_REG, make_lcr (bits, parity, stop, false, false));
102 }
103
104 /* Update interrupt enable register.
105    If our transmit queue is empty, turn off transmit interrupt. */
106 static void
107 write_ier (void) 
108 {
109   outb (IER_REG, intq_empty (&txq) ? 0 : IER_XMIT);
110 }
111
112
113 /* Polls the serial port until it's ready,
114    and then transmits BYTE. */
115 static void
116 putc_poll (uint8_t byte) 
117 {
118   ASSERT (intr_get_level () == INTR_OFF);
119
120   while ((inb (LSR_REG) & LSR_THRE) == 0)
121     continue;
122   outb (THR_REG, byte);
123 }
124
125 /* Serial interrupt handler.
126    As long as we have a byte to transmit,
127    and the hardware is ready to accept a byte for transmission,
128    transmit a byte.
129    Then update interrupt enable register based on queue
130    status. */
131 static void
132 serial_interrupt (struct intr_frame *f UNUSED) 
133 {
134   while (!intq_empty (&txq) && (inb (LSR_REG) & LSR_THRE) != 0) 
135     outb (THR_REG, intq_getc (&txq));
136   write_ier ();
137 }