mirror of
https://github.com/reactos/reactos.git
synced 2024-12-28 10:04:49 +00:00
321 lines
10 KiB
C
321 lines
10 KiB
C
/*
|
|
* 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 <intrin.h>
|
|
#include <ioaccess.h>
|
|
#include <ntstatus.h>
|
|
#include <cportlib/cportlib.h>
|
|
#include <drivers/serial/ns16550.h>
|
|
|
|
/* 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 */
|