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