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