/* * PROJECT: ReactOS ComPort Library * LICENSE: BSD - See COPYING.ARM in the top level directory * FILE: lib/reactos/cportlib/cport.c * PURPOSE: Provides a serial port library for KDCOM, INIT, and FREELDR * PROGRAMMERS: ReactOS Portable Systems Group */ /* NOTE: This library follows the precise serial port intialization steps documented * by Microsoft in some of their Server hardware guidance. Because they've clearly * documented their serial algorithms, we use the same ones to stay "compliant". * Do not change this code to "improve" it. It's done this way on purpose, at least on x86. * -- sir_richard * * REPLY: I reworked the COM-port testing code because the original one * (i.e. the Microsoft's documented one) doesn't work on Virtual PC 2007. * -- hbelusca */ /* NOTE: This code is used by Headless Support (Ntoskrnl.exe and Osloader.exe) and Kdcom.dll in Windows. It may be that WinDBG depends on some of these quirks. */ /* NOTE: The original code supports Modem Control. We currently do not */ /* FIXMEs: - Make this serial-port specific (NS16550 vs other serial port types) - Get x64 KDCOM, KDBG, FREELDR, and other current code to use this */ /* INCLUDES *******************************************************************/ #include #include #include #include #include #define NDEBUG #include /* GLOBALS ********************************************************************/ // Wait timeout value #define TIMEOUT_COUNT 1024 * 200 UCHAR RingIndicator; /* FUNCTIONS ******************************************************************/ VOID NTAPI CpEnableFifo(IN PUCHAR Address, IN BOOLEAN Enable) { /* Set FIFO and clear the receive/transmit buffers */ WRITE_PORT_UCHAR(Address + FIFO_CONTROL_REGISTER, Enable ? SERIAL_FCR_ENABLE | SERIAL_FCR_RCVR_RESET | SERIAL_FCR_TXMT_RESET : SERIAL_FCR_DISABLE); } VOID NTAPI CpSetBaud(IN PCPPORT Port, IN ULONG BaudRate) { UCHAR Lcr; ULONG Mode = CLOCK_RATE / BaudRate; /* Set the DLAB on */ Lcr = READ_PORT_UCHAR(Port->Address + LINE_CONTROL_REGISTER); WRITE_PORT_UCHAR(Port->Address + LINE_CONTROL_REGISTER, Lcr | SERIAL_LCR_DLAB); /* Set the baud rate */ WRITE_PORT_UCHAR(Port->Address + DIVISOR_LATCH_LSB, (UCHAR)(Mode & 0xFF)); WRITE_PORT_UCHAR(Port->Address + DIVISOR_LATCH_MSB, (UCHAR)((Mode >> 8) & 0xFF)); /* Reset DLAB */ WRITE_PORT_UCHAR(Port->Address + LINE_CONTROL_REGISTER, Lcr); /* Save baud rate in port */ Port->BaudRate = BaudRate; } NTSTATUS NTAPI CpInitialize(IN PCPPORT Port, IN PUCHAR Address, IN ULONG BaudRate) { /* Validity checks */ if (Port == NULL || Address == NULL || BaudRate == 0) return STATUS_INVALID_PARAMETER; if (!CpDoesPortExist(Address)) return STATUS_NOT_FOUND; /* Initialize port data */ Port->Address = Address; Port->BaudRate = 0; Port->Flags = 0; /* Disable the interrupts */ WRITE_PORT_UCHAR(Address + LINE_CONTROL_REGISTER, 0); WRITE_PORT_UCHAR(Address + INTERRUPT_ENABLE_REGISTER, 0); /* Turn on DTR, RTS and OUT2 */ WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER, SERIAL_MCR_DTR | SERIAL_MCR_RTS | SERIAL_MCR_OUT2); /* Set the baud rate */ CpSetBaud(Port, BaudRate); /* Set 8 data bits, 1 stop bit, no parity, no break */ WRITE_PORT_UCHAR(Port->Address + LINE_CONTROL_REGISTER, SERIAL_8_DATA | SERIAL_1_STOP | SERIAL_NONE_PARITY); /* Turn on FIFO */ // TODO: Check whether FIFO exists and turn it on in that case. CpEnableFifo(Address, TRUE); // for 16550 /* Read junk out of the RBR */ (VOID)READ_PORT_UCHAR(Address + RECEIVE_BUFFER_REGISTER); return STATUS_SUCCESS; } static BOOLEAN ComPortTest1(IN PUCHAR Address) { /* * See "Building Hardware and Firmware to Complement Microsoft Windows Headless Operation" * Out-of-Band Management Port Device Requirements: * The device must act as a 16550 or 16450 UART. * Windows Server 2003 will test this device using the following process: * 1. Save off the current modem status register. * 2. Place the UART into diagnostic mode (The UART is placed into loopback mode * by writing SERIAL_MCR_LOOP to the modem control register). * 3. The modem status register is read and the high bits are checked. This means * SERIAL_MSR_CTS, SERIAL_MSR_DSR, SERIAL_MSR_RI and SERIAL_MSR_DCD should * all be clear. * 4. Place the UART in diagnostic mode and turn on OUTPUT (Loopback Mode and * OUTPUT are both turned on by writing (SERIAL_MCR_LOOP | SERIAL_MCR_OUT1) * to the modem control register). * 5. The modem status register is read and the ring indicator is checked. * This means SERIAL_MSR_RI should be set. * 6. Restore original modem status register. * * REMARK: Strangely enough, the Virtual PC 2007 virtual machine * doesn't pass this test. */ BOOLEAN RetVal = FALSE; UCHAR Mcr, Msr; /* Save the Modem Control Register */ Mcr = READ_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER); /* Enable loop (diagnostic) mode (set Bit 4 of the MCR) */ WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER, SERIAL_MCR_LOOP); /* Clear all modem output bits */ WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER, SERIAL_MCR_LOOP); /* Read the Modem Status Register */ Msr = READ_PORT_UCHAR(Address + MODEM_STATUS_REGISTER); /* * The upper nibble of the MSR (modem output bits) must be * equal to the lower nibble of the MCR (modem input bits). */ if ((Msr & (SERIAL_MSR_CTS | SERIAL_MSR_DSR | SERIAL_MSR_RI | SERIAL_MSR_DCD)) == 0x00) { /* Set all modem output bits */ WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER, SERIAL_MCR_OUT1 | SERIAL_MCR_LOOP); // Windows /* ReactOS WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER, SERIAL_MCR_DTR | SERIAL_MCR_RTS | SERIAL_MCR_OUT1 | SERIAL_MCR_OUT2 | SERIAL_MCR_LOOP); */ /* Read the Modem Status Register */ Msr = READ_PORT_UCHAR(Address + MODEM_STATUS_REGISTER); /* * The upper nibble of the MSR (modem output bits) must be * equal to the lower nibble of the MCR (modem input bits). */ if (Msr & SERIAL_MSR_RI) // Windows // if (Msr & (SERIAL_MSR_CTS | SERIAL_MSR_DSR | SERIAL_MSR_RI | SERIAL_MSR_DCD) == 0xF0) // ReactOS { RetVal = TRUE; } } /* Restore the MCR */ WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER, Mcr); return RetVal; } static BOOLEAN ComPortTest2(IN PUCHAR Address) { /* * This test checks whether the 16450/16550 scratch register is available. * If not, the serial port is considered as unexisting. */ UCHAR Byte = 0; do { WRITE_PORT_UCHAR(Address + SCRATCH_REGISTER, Byte); if (READ_PORT_UCHAR(Address + SCRATCH_REGISTER) != Byte) return FALSE; } while (++Byte != 0); return TRUE; } BOOLEAN NTAPI CpDoesPortExist(IN PUCHAR Address) { return ( ComPortTest1(Address) || ComPortTest2(Address) ); } UCHAR NTAPI CpReadLsr(IN PCPPORT Port, IN UCHAR ExpectedValue) { UCHAR Lsr, Msr; /* Read the LSR and check if the expected value is present */ Lsr = READ_PORT_UCHAR(Port->Address + LINE_STATUS_REGISTER); if (!(Lsr & ExpectedValue)) { /* Check the MSR for ring indicator toggle */ Msr = READ_PORT_UCHAR(Port->Address + MODEM_STATUS_REGISTER); /* If the indicator reaches 3, we've seen this on/off twice */ RingIndicator |= (Msr & SERIAL_MSR_RI) ? 1 : 2; if (RingIndicator == 3) Port->Flags |= CPPORT_FLAG_MODEM_CONTROL; } return Lsr; } USHORT NTAPI CpGetByte(IN PCPPORT Port, OUT PUCHAR Byte, IN BOOLEAN Wait, IN BOOLEAN Poll) { UCHAR Lsr; ULONG LimitCount = Wait ? TIMEOUT_COUNT : 1; /* Handle early read-before-init */ if (!Port->Address) return CP_GET_NODATA; /* If "wait" mode enabled, spin many times, otherwise attempt just once */ while (LimitCount--) { /* Read LSR for data ready */ Lsr = CpReadLsr(Port, SERIAL_LSR_DR); if ((Lsr & SERIAL_LSR_DR) == SERIAL_LSR_DR) { /* If an error happened, clear the byte and fail */ if (Lsr & (SERIAL_LSR_FE | SERIAL_LSR_PE | SERIAL_LSR_OE)) { *Byte = 0; return CP_GET_ERROR; } /* If only polling was requested by caller, return now */ if (Poll) return CP_GET_SUCCESS; /* Otherwise read the byte and return it */ *Byte = READ_PORT_UCHAR(Port->Address + RECEIVE_BUFFER_REGISTER); /* Handle CD if port is in modem control mode */ if (Port->Flags & CPPORT_FLAG_MODEM_CONTROL) { /* Not implemented yet */ // DPRINT1("CP: CPPORT_FLAG_MODEM_CONTROL unexpected\n"); } /* Byte was read */ return CP_GET_SUCCESS; } } /* Reset LSR, no data was found */ CpReadLsr(Port, 0); return CP_GET_NODATA; } VOID NTAPI CpPutByte(IN PCPPORT Port, IN UCHAR Byte) { /* Check if port is in modem control to handle CD */ // while (Port->Flags & CPPORT_FLAG_MODEM_CONTROL) // Commented for the moment. if (Port->Flags & CPPORT_FLAG_MODEM_CONTROL) // To be removed when this becomes implemented. { /* Not implemented yet */ // DPRINT1("CP: CPPORT_FLAG_MODEM_CONTROL unexpected\n"); } /* Wait for LSR to say we can go ahead */ while ((CpReadLsr(Port, SERIAL_LSR_THRE) & SERIAL_LSR_THRE) == 0x00); /* Send the byte */ WRITE_PORT_UCHAR(Port->Address + TRANSMIT_HOLDING_REGISTER, Byte); } /* EOF */