* Sync to recent trunk (r52563).
[reactos.git] / lib / cportlib / cport.c
1 /*
2 * PROJECT: ReactOS ComPort Library
3 * LICENSE: BSD - See COPYING.ARM in the top level directory
4 * FILE: lib/reactos/cportlib/cport.c
5 * PURPOSE: Provides a serial port library for KDCOM, INIT, and FREELDR
6 * PROGRAMMERS: ReactOS Portable Systems Group
7 */
8
9 /* NOTE: This library follows the precise serial port intialization steps documented
10 * by Microsoft in some of their Server hardware guidance. Because they've clearly
11 * documented their serial algorithms, we use the same ones to stay "compliant".
12 * Do not change this code to "improve" it. It's done this way on purpose, at least on x86.
13 * -- sir_richard
14 */
15
16 /* NOTE: This code is used by Headless Support (Ntoskrnl.exe and Osloader.exe) and
17 Kdcom.dll in Windows. It may be that WinDBG depends on some of these quirks.
18 */
19
20 /* NOTE: The original code supports Modem Control. We currently do not */
21
22 /* FIXMEs:
23 - Make this serial-port specific (NS16550 vs other serial port types)
24 - Get x64 KDCOM, KDBG, FREELDR, and other current code to use this
25 */
26
27 /* INCLUDES *******************************************************************/
28
29 #include <cportlib/cportlib.h>
30 #include <drivers/serial/ns16550.h>
31 #include <intrin.h>
32 #include <ioaccess.h>
33 #include <debug.h>
34
35 /* GLOBALS ********************************************************************/
36
37 UCHAR RingIndicator;
38
39 /* FUNCTIONS ******************************************************************/
40
41 VOID
42 NTAPI
43 CpInitialize(IN PCPPORT Port,
44 IN PUCHAR Address,
45 IN ULONG Rate)
46 {
47 /* Reset port data */
48 Port->Address = Address;
49 Port->Baud = 0;
50
51 /* Set the baud rate */
52 CpSetBaud(Port, Rate);
53
54 /* Enable on DTR and RTS */
55 WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER,
56 SERIAL_MCR_DTR | SERIAL_MCR_RTS);
57
58 /* Disable interrupts */
59 WRITE_PORT_UCHAR(Address + INTERRUPT_ENABLE_REGISTER, 0);
60 }
61
62 VOID
63 NTAPI
64 CpEnableFifo(IN PUCHAR Address,
65 IN BOOLEAN Enable)
66 {
67 /* Set FIFO */
68 WRITE_PORT_UCHAR(Address + FIFO_CONTROL_REGISTER, Enable ? SERIAL_FCR_ENABLE : 0);
69 }
70
71 BOOLEAN
72 NTAPI
73 CpDoesPortExist(IN PUCHAR Address)
74 {
75 UCHAR Old;
76 /*
77 * See "Building Hardware and Firmware to Complement Microsoft Windows Headless Operation"
78 * Out-of-Band Management Port Device Requirements:
79 * The device must act as a 16550 or 16450 UART.
80 * Windows Server 2003 will test this device using the following process.
81 * 1. Save off the current modem status register.
82 * 2. Place the UART into diagnostic mode (The UART is placed into loopback mode
83 * by writing SERIAL_MCR_LOOP to the modem control register).
84 * 3. The modem status register is read and the high bits are checked. This means
85 * SERIAL_MSR_CTS, SERIAL_MSR_DSR, SERIAL_MSR_RI and SERIAL_MSR_DCD should
86 * all be clear.
87 * 4. Place the UART in diagnostic mode and turn on OUTPUT (Loopback Mode and
88 * OUTPUT are both turned on by writing (SERIAL_MCR_LOOP | SERIAL_MCR_OUT1)
89 * to the modem control register).
90 * 5. The modem status register is read and the ring indicator is checked.
91 * This means SERIAL_MSR_RI should be set.
92 * 6. Restore original modem status register.
93 */
94 Old = READ_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER);
95 WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER, SERIAL_MCR_LOOP);
96 WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER, SERIAL_MCR_LOOP);
97 if (!(READ_PORT_UCHAR(Address + MODEM_STATUS_REGISTER) &
98 (SERIAL_MSR_CTS | SERIAL_MSR_DSR | SERIAL_MSR_RI | SERIAL_MSR_DCD)))
99 {
100 WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER,
101 (SERIAL_MCR_OUT1 | SERIAL_MCR_LOOP));
102 if (READ_PORT_UCHAR(Address + MODEM_STATUS_REGISTER) & SERIAL_MSR_RI)
103 {
104 WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER, Old);
105 return TRUE;
106 }
107 }
108 WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER, Old);
109 return FALSE;
110 }
111
112 UCHAR
113 NTAPI
114 CpReadLsr(IN PCPPORT Port,
115 IN UCHAR ExpectedValue)
116 {
117 UCHAR Lsr, Msr;
118
119 /* Read the LSR and check if the expected value is present */
120 Lsr = READ_PORT_UCHAR(Port->Address + LINE_STATUS_REGISTER);
121 if (!(Lsr & ExpectedValue))
122 {
123 /* Check the MSR for ring indicator toggle */
124 Msr = READ_PORT_UCHAR(Port->Address + MODEM_STATUS_REGISTER);
125
126 /* If the indicator reaches 3, we've seen this on/off twice */
127 RingIndicator |= (Msr & SERIAL_MSR_RI) ? 1 : 2;
128 if (RingIndicator == 3) Port->Flags |= CPPORT_FLAG_MODEM_CONTROL;
129 }
130
131 return Lsr;
132 }
133
134 VOID
135 NTAPI
136 CpSetBaud(IN PCPPORT Port,
137 IN ULONG Rate)
138 {
139 UCHAR Lcr;
140 USHORT Mode;
141
142 /* Add DLAB */
143 Lcr = READ_PORT_UCHAR(Port->Address + LINE_CONTROL_REGISTER);
144 WRITE_PORT_UCHAR(Port->Address + LINE_CONTROL_REGISTER, Lcr | SERIAL_LCR_DLAB);
145
146 /* Set baud rate */
147 Mode = 115200 / Rate;
148 WRITE_PORT_UCHAR(Port->Address + DIVISOR_LATCH_MSB, (UCHAR)((Mode >> 8) & 0xff));
149 WRITE_PORT_UCHAR(Port->Address + DIVISOR_LATCH_LSB, (UCHAR)(Mode & 0xff));
150
151 /* Reset DLAB and set 8 data bits, 1 stop bit, no parity, no break */
152 WRITE_PORT_UCHAR(Port->Address + LINE_CONTROL_REGISTER,
153 SERIAL_8_DATA | SERIAL_1_STOP | SERIAL_NONE_PARITY);
154
155 /* Save baud rate in port */
156 Port->Baud = Rate;
157 }
158
159 USHORT
160 NTAPI
161 CpGetByte(IN PCPPORT Port,
162 IN PUCHAR Byte,
163 IN BOOLEAN Wait,
164 IN BOOLEAN Poll)
165 {
166 UCHAR Lsr;
167 ULONG i;
168
169 /* Handle early read-before-init */
170 if (!Port->Address) return CP_GET_NODATA;
171
172 /* If "wait" mode enabled, spin many times, otherwise attempt just once */
173 i = Wait ? 204800 : 1;
174 while (i--)
175 {
176 /* Read LSR for data ready */
177 Lsr = CpReadLsr(Port, SERIAL_LSR_DR);
178 if ((Lsr & SERIAL_LSR_DR) == SERIAL_LSR_DR)
179 {
180 /* If an error happened, clear the byte and fail */
181 if (Lsr & (SERIAL_LSR_FE | SERIAL_LSR_PE))
182 {
183 *Byte = 0;
184 return CP_GET_ERROR;
185 }
186
187 /* If only polling was requested by caller, return now */
188 if (Poll) return CP_GET_SUCCESS;
189
190 /* Otherwise read the byte and return it */
191 *Byte = READ_PORT_UCHAR(Port->Address + RECEIVE_BUFFER_REGISTER);
192
193 /* Handle CD if port is in modem control mode */
194 if (Port->Flags & CPPORT_FLAG_MODEM_CONTROL)
195 {
196 /* Not implemented yet */
197 DPRINT1("CP: CPPORT_FLAG_MODEM_CONTROL unexpected\n");
198 }
199
200 /* Byte was read */
201 return CP_GET_SUCCESS;
202 }
203 }
204
205 /* Reset LSR, no data was found */
206 CpReadLsr(Port, 0);
207 return CP_GET_NODATA;
208 }
209
210 VOID
211 NTAPI
212 CpPutByte(IN PCPPORT Port,
213 IN UCHAR Byte)
214 {
215 /* Check if port is in modem control to handle CD */
216 while (Port->Flags & CPPORT_FLAG_MODEM_CONTROL)
217 {
218 /* Not implemented yet */
219 DPRINT1("CP: CPPORT_FLAG_MODEM_CONTROL unexpected\n");
220 }
221
222 /* Wait for LSR to say we can go ahead */
223 while (!(CpReadLsr(Port, SERIAL_LSR_THRE) & SERIAL_LSR_THRE));
224
225 /* Send the byte */
226 WRITE_PORT_UCHAR(Port->Address + RECEIVE_BUFFER_REGISTER, Byte);
227 }