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