Synchronize with trunk revision 59781.
[reactos.git] / lib / cportlib / cport.c
index 75a4c03..4cad5b5 100644 (file)
  * 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
 /* 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
+    - 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 <cportlib/cportlib.h>
 #include <drivers/serial/ns16550.h>
 #include <intrin.h>
 #include <ioaccess.h>
+#include <ntstatus.h>
+
+#define NDEBUG
 #include <debug.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 Rate)
+             IN PUCHAR  Address,
+             IN ULONG   BaudRate)
 {
-       /* Reset port data */
-    Port->Address = Address;
-    Port->Baud = 0;
+    /* Validity checks */
+    if (Port == NULL || Address == NULL || BaudRate == 0)
+        return STATUS_INVALID_PARAMETER;
 
-       /* Set the baud rate */
-    CpSetBaud(Port, Rate);
+    if (!CpDoesPortExist(Address))
+        return STATUS_NOT_FOUND;
 
-    /* Enable on DTR and RTS  */
-    WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER,
-                                        SERIAL_MCR_DTR | SERIAL_MCR_RTS);
+    /* Initialize port data */
+    Port->Address  = Address;
+    Port->BaudRate = 0;
+    Port->Flags    = 0;
 
-    /* Disable interrupts */
+    /* 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;
 }
 
-VOID
-NTAPI
-CpEnableFifo(IN PUCHAR Address,
-                IN BOOLEAN Enable)
+static BOOLEAN
+ComPortTest1(IN PUCHAR Address)
 {
-       /* Set FIFO */
-    WRITE_PORT_UCHAR(Address + FIFO_CONTROL_REGISTER, Enable ? SERIAL_FCR_ENABLE : 0);
+    /*
+     * 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)
 {
-    UCHAR Old;
-       /*
-        * 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.
-        */
-    Old = READ_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER);
-    WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER, SERIAL_MCR_LOOP);
-    WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER, SERIAL_MCR_LOOP);
-    if (!(READ_PORT_UCHAR(Address + MODEM_STATUS_REGISTER) &
-                (SERIAL_MSR_CTS | SERIAL_MSR_DSR | SERIAL_MSR_RI | SERIAL_MSR_DCD)))
-       {
-           WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER,
-                                                (SERIAL_MCR_OUT1 | SERIAL_MCR_LOOP));
-           if (READ_PORT_UCHAR(Address + MODEM_STATUS_REGISTER) & SERIAL_MSR_RI)
-               {
-                       WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER, Old);
-                       return TRUE;
-           }
-       }
-    WRITE_PORT_UCHAR(Address + MODEM_CONTROL_REGISTER, Old);
-    return FALSE;
+    return ( ComPortTest1(Address) || ComPortTest2(Address) );
 }
 
 UCHAR
 NTAPI
 CpReadLsr(IN PCPPORT Port,
-         IN UCHAR ExpectedValue)
+          IN UCHAR   ExpectedValue)
 {
-       UCHAR Lsr, Msr;
+    UCHAR Lsr, Msr;
 
-       /* Read the LSR and check if the expected value is present */
+    /* 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 */
+    {
+        /* 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 */
+        /* 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;
     }
@@ -131,78 +251,52 @@ CpReadLsr(IN PCPPORT Port,
     return Lsr;
 }
 
-VOID
+USHORT
 NTAPI
-CpSetBaud(IN PCPPORT Port,
-         IN ULONG Rate)
+CpGetByte(IN  PCPPORT Port,
+          OUT PUCHAR  Byte,
+          IN  BOOLEAN Wait,
+          IN BOOLEAN Poll)
 {
-       UCHAR Lcr;
-       USHORT Mode;
-
-    /* Add DLAB */
-       Lcr = READ_PORT_UCHAR(Port->Address + LINE_CONTROL_REGISTER);
-    WRITE_PORT_UCHAR(Port->Address + LINE_CONTROL_REGISTER, Lcr | SERIAL_LCR_DLAB);
+    UCHAR Lsr;
+    ULONG LimitCount = Wait ? TIMEOUT_COUNT : 1;
 
-    /* Set baud rate */
-    Mode = 115200 / Rate;
-    WRITE_PORT_UCHAR(Port->Address + DIVISOR_LATCH_MSB, (UCHAR)((Mode >> 8) & 0xff));
-    WRITE_PORT_UCHAR(Port->Address + DIVISOR_LATCH_LSB, (UCHAR)(Mode & 0xff));
+    /* Handle early read-before-init */
+    if (!Port->Address) return CP_GET_NODATA;
 
-    /* Reset DLAB and 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);
-
-       /* Save baud rate in port */
-    Port->Baud = Rate;
-}
-
-USHORT
-NTAPI
-CpGetByte(IN PCPPORT Port,
-             IN PUCHAR Byte,
-                 IN BOOLEAN Wait,
-                 IN BOOLEAN Poll)
-{
-       UCHAR Lsr;
-       ULONG i;
-
-       /* Handle early read-before-init */
-       if (!Port->Address) return CP_GET_NODATA;
-       
-       /* If "wait" mode enabled, spin many times, otherwise attempt just once */
-       i = Wait ? 204800 : 1;
-    while (i--)
-       {
-               /* Read LSR for data ready */
+    /* 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))
-                       {
+        {
+            /* 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 only polling was requested by caller, return now */
             if (Poll) return CP_GET_SUCCESS;
 
-                       /* Otherwise read the byte and return it */
+            /* 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 */
+            /* 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");
+            {
+                /* Not implemented yet */
+                DPRINT1("CP: CPPORT_FLAG_MODEM_CONTROL unexpected\n");
             }
 
-                       /* Byte was read */
+            /* Byte was read */
             return CP_GET_SUCCESS;
         }
     }
 
-       /* Reset LSR, no data was found */
+    /* Reset LSR, no data was found */
     CpReadLsr(Port, 0);
     return CP_GET_NODATA;
 }
@@ -210,18 +304,21 @@ CpGetByte(IN PCPPORT Port,
 VOID
 NTAPI
 CpPutByte(IN PCPPORT Port,
-         IN UCHAR Byte)
+          IN UCHAR   Byte)
 {
-       /* Check if port is in modem control to handle CD */
-    while (Port->Flags & CPPORT_FLAG_MODEM_CONTROL)
-       {
-               /* Not implemented yet */
-               DPRINT1("CP: CPPORT_FLAG_MODEM_CONTROL unexpected\n");
+    /* 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));
+    /* 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 + RECEIVE_BUFFER_REGISTER, Byte);
+    WRITE_PORT_UCHAR(Port->Address + TRANSMIT_HOLDING_REGISTER, Byte);
 }
+
+/* EOF */