Sync with trunk r63192.
[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 * REPLY: I reworked the COM-port testing code because the original one
16 * (i.e. the Microsoft's documented one) doesn't work on Virtual PC 2007.
17 * -- hbelusca
18 */
19
20 /* NOTE: This code is used by Headless Support (Ntoskrnl.exe and Osloader.exe) and
21 Kdcom.dll in Windows. It may be that WinDBG depends on some of these quirks.
22 */
23
24 /* NOTE: The original code supports Modem Control. We currently do not */
25
26 /* FIXMEs:
27 - Make this serial-port specific (NS16550 vs other serial port types)
28 - Get x64 KDCOM, KDBG, FREELDR, and other current code to use this
29 */
30
31 /* INCLUDES *******************************************************************/
32
33 #include <cportlib/cportlib.h>
34 #include <drivers/serial/ns16550.h>
35 #include <intrin.h>
36 #include <ioaccess.h>
37 #include <ntstatus.h>
38
39 #define NDEBUG
40 #include <debug.h>
41
42 /* GLOBALS ********************************************************************/
43
44 // Wait timeout value
45 #define TIMEOUT_COUNT 1024 * 200
46
47 UCHAR RingIndicator;
48
49
50 /* FUNCTIONS ******************************************************************/
51
52 VOID
53 NTAPI
54 CpEnableFifo(IN PUCHAR Address,
55 IN BOOLEAN Enable)
56 {
57 /* Set FIFO and clear the receive/transmit buffers */
58 WRITE_PORT_UCHAR(Address + FIFO_CONTROL_REGISTER,
59 Enable ? SERIAL_FCR_ENABLE | SERIAL_FCR_RCVR_RESET | SERIAL_FCR_TXMT_RESET
60 : SERIAL_FCR_DISABLE);
61 }
62
63 VOID
64 NTAPI
65 CpSetBaud(IN PCPPORT Port,
66 IN ULONG BaudRate)
67 {
68 UCHAR Lcr;
69 ULONG Mode = CLOCK_RATE / BaudRate;
70
71 /* Set the DLAB on */
72 Lcr = READ_PORT_UCHAR(Port->Address + LINE_CONTROL_REGISTER);
73 WRITE_PORT_UCHAR(Port->Address + LINE_CONTROL_REGISTER, Lcr | SERIAL_LCR_DLAB);
74
75 /* Set the baud rate */
76 WRITE_PORT_UCHAR(Port->Address + DIVISOR_LATCH_LSB, (UCHAR)(Mode & 0xFF));
77 WRITE_PORT_UCHAR(Port->Address + DIVISOR_LATCH_MSB, (UCHAR)((Mode >> 8) & 0xFF));
78
79 /* Reset DLAB */
80 WRITE_PORT_UCHAR(Port->Address + LINE_CONTROL_REGISTER, Lcr);
81
82 /* Save baud rate in port */
83 Port->BaudRate = BaudRate;
84 }
85
86 NTSTATUS
87 NTAPI
88 CpInitialize(IN PCPPORT Port,
89 IN PUCHAR Address,
90 IN ULONG BaudRate)
91 {
92 /* Validity checks */
93 if (Port == NULL || Address == NULL || BaudRate == 0)
94 return STATUS_INVALID_PARAMETER;
95
96 if (!CpDoesPortExist(Address))
97 return STATUS_NOT_FOUND;
98
99 /* Initialize port data */
100 Port->Address = Address;
101 Port->BaudRate = 0;
102 Port->Flags = 0;
103
104 /* Disable the interrupts */
105 WRITE_PORT_UCHAR(Address + LINE_CONTROL_REGISTER, 0);
106 WRITE_PORT_UCHAR(Address + INTERRUPT_ENABLE_REGISTER, 0);
107
108 /* Turn on DTR, RTS and OUT2 */
109 WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER,
110 SERIAL_MCR_DTR | SERIAL_MCR_RTS | SERIAL_MCR_OUT2);
111
112 /* Set the baud rate */
113 CpSetBaud(Port, BaudRate);
114
115 /* Set 8 data bits, 1 stop bit, no parity, no break */
116 WRITE_PORT_UCHAR(Port->Address + LINE_CONTROL_REGISTER,
117 SERIAL_8_DATA | SERIAL_1_STOP | SERIAL_NONE_PARITY);
118
119 /* Turn on FIFO */
120 // TODO: Check whether FIFO exists and turn it on in that case.
121 CpEnableFifo(Address, TRUE); // for 16550
122
123 /* Read junk out of the RBR */
124 (VOID)READ_PORT_UCHAR(Address + RECEIVE_BUFFER_REGISTER);
125
126 return STATUS_SUCCESS;
127 }
128
129 static BOOLEAN
130 ComPortTest1(IN PUCHAR Address)
131 {
132 /*
133 * See "Building Hardware and Firmware to Complement Microsoft Windows Headless Operation"
134 * Out-of-Band Management Port Device Requirements:
135 * The device must act as a 16550 or 16450 UART.
136 * Windows Server 2003 will test this device using the following process:
137 * 1. Save off the current modem status register.
138 * 2. Place the UART into diagnostic mode (The UART is placed into loopback mode
139 * by writing SERIAL_MCR_LOOP to the modem control register).
140 * 3. The modem status register is read and the high bits are checked. This means
141 * SERIAL_MSR_CTS, SERIAL_MSR_DSR, SERIAL_MSR_RI and SERIAL_MSR_DCD should
142 * all be clear.
143 * 4. Place the UART in diagnostic mode and turn on OUTPUT (Loopback Mode and
144 * OUTPUT are both turned on by writing (SERIAL_MCR_LOOP | SERIAL_MCR_OUT1)
145 * to the modem control register).
146 * 5. The modem status register is read and the ring indicator is checked.
147 * This means SERIAL_MSR_RI should be set.
148 * 6. Restore original modem status register.
149 *
150 * REMARK: Strangely enough, the Virtual PC 2007 virtual machine
151 * doesn't pass this test.
152 */
153
154 BOOLEAN RetVal = FALSE;
155 UCHAR Mcr, Msr;
156
157 /* Save the Modem Control Register */
158 Mcr = READ_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER);
159
160 /* Enable loop (diagnostic) mode (set Bit 4 of the MCR) */
161 WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER, SERIAL_MCR_LOOP);
162
163 /* Clear all modem output bits */
164 WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER, SERIAL_MCR_LOOP);
165
166 /* Read the Modem Status Register */
167 Msr = READ_PORT_UCHAR(Address + MODEM_STATUS_REGISTER);
168
169 /*
170 * The upper nibble of the MSR (modem output bits) must be
171 * equal to the lower nibble of the MCR (modem input bits).
172 */
173 if ((Msr & (SERIAL_MSR_CTS | SERIAL_MSR_DSR | SERIAL_MSR_RI | SERIAL_MSR_DCD)) == 0x00)
174 {
175 /* Set all modem output bits */
176 WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER,
177 SERIAL_MCR_OUT1 | SERIAL_MCR_LOOP); // Windows
178 /* ReactOS
179 WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER,
180 SERIAL_MCR_DTR | SERIAL_MCR_RTS | SERIAL_MCR_OUT1 | SERIAL_MCR_OUT2 | SERIAL_MCR_LOOP);
181 */
182
183 /* Read the Modem Status Register */
184 Msr = READ_PORT_UCHAR(Address + MODEM_STATUS_REGISTER);
185
186 /*
187 * The upper nibble of the MSR (modem output bits) must be
188 * equal to the lower nibble of the MCR (modem input bits).
189 */
190 if (Msr & SERIAL_MSR_RI) // Windows
191 // if (Msr & (SERIAL_MSR_CTS | SERIAL_MSR_DSR | SERIAL_MSR_RI | SERIAL_MSR_DCD) == 0xF0) // ReactOS
192 {
193 RetVal = TRUE;
194 }
195 }
196
197 /* Restore the MCR */
198 WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER, Mcr);
199
200 return RetVal;
201 }
202
203 static BOOLEAN
204 ComPortTest2(IN PUCHAR Address)
205 {
206 /*
207 * This test checks whether the 16450/16550 scratch register is available.
208 * If not, the serial port is considered as unexisting.
209 */
210
211 UCHAR Byte = 0;
212
213 do
214 {
215 WRITE_PORT_UCHAR(Address + SCRATCH_REGISTER, Byte);
216
217 if (READ_PORT_UCHAR(Address + SCRATCH_REGISTER) != Byte)
218 return FALSE;
219
220 } while (++Byte != 0);
221
222 return TRUE;
223 }
224
225 BOOLEAN
226 NTAPI
227 CpDoesPortExist(IN PUCHAR Address)
228 {
229 return ( ComPortTest1(Address) || ComPortTest2(Address) );
230 }
231
232 UCHAR
233 NTAPI
234 CpReadLsr(IN PCPPORT Port,
235 IN UCHAR ExpectedValue)
236 {
237 UCHAR Lsr, Msr;
238
239 /* Read the LSR and check if the expected value is present */
240 Lsr = READ_PORT_UCHAR(Port->Address + LINE_STATUS_REGISTER);
241 if (!(Lsr & ExpectedValue))
242 {
243 /* Check the MSR for ring indicator toggle */
244 Msr = READ_PORT_UCHAR(Port->Address + MODEM_STATUS_REGISTER);
245
246 /* If the indicator reaches 3, we've seen this on/off twice */
247 RingIndicator |= (Msr & SERIAL_MSR_RI) ? 1 : 2;
248 if (RingIndicator == 3) Port->Flags |= CPPORT_FLAG_MODEM_CONTROL;
249 }
250
251 return Lsr;
252 }
253
254 USHORT
255 NTAPI
256 CpGetByte(IN PCPPORT Port,
257 OUT PUCHAR Byte,
258 IN BOOLEAN Wait,
259 IN BOOLEAN Poll)
260 {
261 UCHAR Lsr;
262 ULONG LimitCount = Wait ? TIMEOUT_COUNT : 1;
263
264 /* Handle early read-before-init */
265 if (!Port->Address) return CP_GET_NODATA;
266
267 /* If "wait" mode enabled, spin many times, otherwise attempt just once */
268 while (LimitCount--)
269 {
270 /* Read LSR for data ready */
271 Lsr = CpReadLsr(Port, SERIAL_LSR_DR);
272 if ((Lsr & SERIAL_LSR_DR) == SERIAL_LSR_DR)
273 {
274 /* If an error happened, clear the byte and fail */
275 if (Lsr & (SERIAL_LSR_FE | SERIAL_LSR_PE | SERIAL_LSR_OE))
276 {
277 *Byte = 0;
278 return CP_GET_ERROR;
279 }
280
281 /* If only polling was requested by caller, return now */
282 if (Poll) return CP_GET_SUCCESS;
283
284 /* Otherwise read the byte and return it */
285 *Byte = READ_PORT_UCHAR(Port->Address + RECEIVE_BUFFER_REGISTER);
286
287 /* Handle CD if port is in modem control mode */
288 if (Port->Flags & CPPORT_FLAG_MODEM_CONTROL)
289 {
290 /* Not implemented yet */
291 // DPRINT1("CP: CPPORT_FLAG_MODEM_CONTROL unexpected\n");
292 }
293
294 /* Byte was read */
295 return CP_GET_SUCCESS;
296 }
297 }
298
299 /* Reset LSR, no data was found */
300 CpReadLsr(Port, 0);
301 return CP_GET_NODATA;
302 }
303
304 VOID
305 NTAPI
306 CpPutByte(IN PCPPORT Port,
307 IN UCHAR Byte)
308 {
309 /* Check if port is in modem control to handle CD */
310 // while (Port->Flags & CPPORT_FLAG_MODEM_CONTROL) // Commented for the moment.
311 if (Port->Flags & CPPORT_FLAG_MODEM_CONTROL) // To be removed when this becomes implemented.
312 {
313 /* Not implemented yet */
314 // DPRINT1("CP: CPPORT_FLAG_MODEM_CONTROL unexpected\n");
315 }
316
317 /* Wait for LSR to say we can go ahead */
318 while ((CpReadLsr(Port, SERIAL_LSR_THRE) & SERIAL_LSR_THRE) == 0x00);
319
320 /* Send the byte */
321 WRITE_PORT_UCHAR(Port->Address + TRANSMIT_HOLDING_REGISTER, Byte);
322 }
323
324 /* EOF */