xref: /reactos/sdk/lib/cportlib/cport.c (revision c2c66aff)
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 */
325