/* * PROJECT: ReactOS ComPort Library for NEC PC-98 series * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) * PURPOSE: Provides a serial port library for KDCOM, INIT, and FREELDR * COPYRIGHT: Copyright 2020 Dmitry Borisov (di.sean@protonmail.com) */ /* Note: ns16550 code from cportlib.c */ /* INCLUDES *******************************************************************/ #include #include #include #include #include #include #include #include /* GLOBALS ********************************************************************/ #define TIMEOUT_COUNT 1024 * 200 static struct { PUCHAR Address; BOOLEAN HasFifo; BOOLEAN FifoEnabled; UCHAR RingIndicator; } Rs232ComPort[] = { { (PUCHAR)0x030, FALSE, FALSE, 0 }, { (PUCHAR)0x238, FALSE, FALSE, 0 } }; static BOOLEAN IsNekoProject = FALSE; /* FUNCTIONS ******************************************************************/ static BOOLEAN CpIsNekoProject(VOID) { UCHAR Input[4] = "NP2"; UCHAR Output[4] = {0}; UCHAR i; for (i = 0; i < 3; i++) WRITE_PORT_UCHAR((PUCHAR)0x7EF, Input[i]); for (i = 0; i < 3; i++) Output[i] = READ_PORT_UCHAR((PUCHAR)0x7EF); return (*(PULONG)Input == *(PULONG)Output); } static VOID CpWait(VOID) { UCHAR i; for (i = 0; i < 6; i++) WRITE_PORT_UCHAR((PUCHAR)CPU_IO_o_ARTIC_DELAY, 0); } VOID NTAPI CpEnableFifo( IN PUCHAR Address, IN BOOLEAN Enable) { /* Set FIFO and clear the receive/transmit buffers */ if (Address == Rs232ComPort[0].Address && Rs232ComPort[0].HasFifo) { if (Enable) WRITE_PORT_UCHAR((PUCHAR)SER1_IO_o_FIFO_CONTROL, SER_FCR_ENABLE | SER_FCR_RCVR_RESET | SER_FCR_TXMT_RESET); else WRITE_PORT_UCHAR((PUCHAR)SER1_IO_o_FIFO_CONTROL, SER_FCR_DISABLE); Rs232ComPort[0].FifoEnabled = Enable; } else if (Address == Rs232ComPort[1].Address && Rs232ComPort[1].HasFifo) { if (Enable) WRITE_PORT_UCHAR((PUCHAR)SER2_IO_o_FIFO_CONTROL, SER_FCR_ENABLE | SER_FCR_RCVR_RESET | SER_FCR_TXMT_RESET); else WRITE_PORT_UCHAR((PUCHAR)SER2_IO_o_FIFO_CONTROL, SER_FCR_DISABLE); Rs232ComPort[1].FifoEnabled = Enable; } CpWait(); } VOID NTAPI CpSetBaud( IN PCPPORT Port, IN ULONG BaudRate) { UCHAR Lcr; USHORT Count; TIMER_CONTROL_PORT_REGISTER TimerControl; if (Port->Address == Rs232ComPort[0].Address) { if (Rs232ComPort[0].HasFifo) WRITE_PORT_UCHAR((PUCHAR)SER1_IO_o_DIVISOR_LATCH, SER1_DLR_MODE_LEGACY); TimerControl.BcdMode = FALSE; TimerControl.OperatingMode = PitOperatingMode3; TimerControl.AccessMode = PitAccessModeLowHigh; TimerControl.Channel = PitChannel2; if (IsNekoProject) { /* The horrible text input lag happens by about 6 seconds on my PC */ Count = 3; } else { Count = (READ_PORT_UCHAR((PUCHAR)0x42) & 0x20) ? (TIMER_FREQUENCY_1 / (BaudRate * 16)) : (TIMER_FREQUENCY_2 / (BaudRate * 16)); } Write8253Timer(TimerControl, Count); /* Save baud rate in port */ Port->BaudRate = BaudRate; } else if (Port->Address == Rs232ComPort[1].Address) { /* Set the DLAB on */ Lcr = READ_PORT_UCHAR((PUCHAR)SER2_IO_i_LINE_CONTROL); WRITE_PORT_UCHAR((PUCHAR)SER2_IO_o_LINE_CONTROL, Lcr | SER2_LCR_DLAB); /* Set the baud rate */ Count = SER2_CLOCK_RATE / BaudRate; WRITE_PORT_UCHAR((PUCHAR)SER2_IO_o_DIVISOR_LATCH_LSB, Count & 0xFF); WRITE_PORT_UCHAR((PUCHAR)SER2_IO_o_DIVISOR_LATCH_MSB, (Count >> 8) & 0xFF); /* Reset DLAB */ WRITE_PORT_UCHAR((PUCHAR)SER2_IO_o_LINE_CONTROL, Lcr & ~SER2_LCR_DLAB); /* Save baud rate in port */ Port->BaudRate = BaudRate; } } NTSTATUS NTAPI CpInitialize( IN PCPPORT Port, IN PUCHAR Address, IN ULONG BaudRate) { SYSTEM_CONTROL_PORT_C_REGISTER SystemControl; UCHAR FifoStatus; 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; IsNekoProject = CpIsNekoProject(); if (Port->Address == Rs232ComPort[0].Address) { /* FIFO test */ FifoStatus = READ_PORT_UCHAR((PUCHAR)SER1_IO_i_INTERRUPT_ID) & SER1_IIR_FIFOS_ENABLED; CpWait(); Rs232ComPort[0].HasFifo = ((READ_PORT_UCHAR((PUCHAR)SER1_IO_i_INTERRUPT_ID) & SER1_IIR_FIFOS_ENABLED) != FifoStatus); /* Disable the interrupts */ SystemControl.Bits = READ_PORT_UCHAR((PUCHAR)PPI_IO_i_PORT_C); SystemControl.InterruptEnableRxReady = FALSE; SystemControl.InterruptEnableTxEmpty = FALSE; SystemControl.InterruptEnableTxReady = FALSE; WRITE_PORT_UCHAR((PUCHAR)PPI_IO_o_PORT_C, SystemControl.Bits); /* Turn off FIFO */ if (Rs232ComPort[0].HasFifo) CpEnableFifo(Address, FALSE); /* Set the baud rate */ CpSetBaud(Port, BaudRate); /* Software reset */ WRITE_PORT_UCHAR((PUCHAR)SER1_IO_o_MODE_COMMAND, 0); CpWait(); WRITE_PORT_UCHAR((PUCHAR)SER1_IO_o_MODE_COMMAND, 0); CpWait(); WRITE_PORT_UCHAR((PUCHAR)SER1_IO_o_MODE_COMMAND, 0); CpWait(); WRITE_PORT_UCHAR((PUCHAR)SER1_IO_o_MODE_COMMAND, SER1_COMMMAND_IR); CpWait(); /* Mode instruction - asynchronous mode, 8 data bits, 1 stop bit, no parity, 16x clock divisor */ WRITE_PORT_UCHAR((PUCHAR)SER1_IO_o_MODE_COMMAND, SER1_MODE_LENGTH_8 | SER1_MODE_1_STOP | SER1_MODE_CLOCKx16); CpWait(); /* Command instruction - transmit enable, turn on DTR and RTS, receive enable, clear error flag */ WRITE_PORT_UCHAR((PUCHAR)SER1_IO_o_MODE_COMMAND, SER1_COMMMAND_TxEN | SER1_COMMMAND_DTR | SER1_COMMMAND_RxEN | SER1_COMMMAND_ER | SER1_COMMMAND_RTS); CpWait(); /* Disable the interrupts again */ WRITE_PORT_UCHAR((PUCHAR)PPI_IO_o_PORT_C, SystemControl.Bits); /* Turn on FIFO */ if (Rs232ComPort[0].HasFifo) CpEnableFifo(Address, TRUE); /* Read junk out of the data register */ if (Rs232ComPort[0].HasFifo) (VOID)READ_PORT_UCHAR((PUCHAR)SER1_IO_i_RECEIVER_BUFFER); else (VOID)READ_PORT_UCHAR((PUCHAR)SER1_IO_i_DATA); return STATUS_SUCCESS; } else if (Port->Address == Rs232ComPort[1].Address) { /* Disable the interrupts */ WRITE_PORT_UCHAR((PUCHAR)SER2_IO_o_LINE_CONTROL, 0); WRITE_PORT_UCHAR((PUCHAR)SER2_IO_o_INTERRUPT_EN, 0); /* Turn on DTR, RTS and OUT2 */ WRITE_PORT_UCHAR((PUCHAR)SER2_IO_o_MODEM_CONTROL, SER2_MCR_DTR_STATE | SER2_MCR_RTS_STATE | SER2_MCR_OUT_2); /* Set the baud rate */ CpSetBaud(Port, BaudRate); /* Set 8 data bits, 1 stop bit, no parity, no break */ WRITE_PORT_UCHAR((PUCHAR)SER2_IO_o_LINE_CONTROL, SER2_LCR_LENGTH_8 | SER2_LCR_ST1 | SER2_LCR_NO_PARITY); /* FIFO test */ Rs232ComPort[1].HasFifo = READ_PORT_UCHAR((PUCHAR)SER2_IO_i_INTERRUPT_ID) & SER2_IIR_HAS_FIFO; /* Turn on FIFO */ if (Rs232ComPort[1].HasFifo) CpEnableFifo(Address, TRUE); /* Read junk out of the RBR */ (VOID)READ_PORT_UCHAR((PUCHAR)SER2_IO_i_RECEIVER_BUFFER); return STATUS_SUCCESS; } return STATUS_NOT_FOUND; } 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((PUCHAR)SER2_IO_i_MODEM_CONTROL); /* Enable loop (diagnostic) mode (set Bit 4 of the MCR) */ WRITE_PORT_UCHAR((PUCHAR)SER2_IO_o_MODEM_CONTROL, SER2_MCR_LOOPBACK); /* Clear all modem output bits */ WRITE_PORT_UCHAR((PUCHAR)SER2_IO_o_MODEM_CONTROL, SER2_MCR_LOOPBACK); /* Read the Modem Status Register */ Msr = READ_PORT_UCHAR((PUCHAR)SER2_IO_i_MODEM_STATUS); /* * The upper nibble of the MSR (modem output bits) must be * equal to the lower nibble of the MCR (modem input bits). */ if ((Msr & (SER_MSR_CTS | SER_MSR_DSR | SER_MSR_RI | SER_MSR_DCD)) == 0x00) { /* Set all modem output bits */ WRITE_PORT_UCHAR((PUCHAR)SER2_IO_o_MODEM_CONTROL, SER2_MCR_OUT_1 | SER2_MCR_LOOPBACK); // Windows /* ReactOS WRITE_PORT_UCHAR((PUCHAR)SER2_IO_o_MODEM_CONTROL, SER2_MCR_DTR_STATE | SER2_MCR_RTS_STATE | SER2_MCR_OUT_1 | SER2_MCR_OUT_2 | SER2_MCR_LOOPBACK); */ /* Read the Modem Status Register */ Msr = READ_PORT_UCHAR((PUCHAR)SER2_IO_i_MODEM_STATUS); /* * The upper nibble of the MSR (modem output bits) must be * equal to the lower nibble of the MCR (modem input bits). */ if (Msr & SER_MSR_RI) // Windows // if (Msr & (SER_MSR_CTS | SER_MSR_DSR | SER_MSR_RI | SER_MSR_DCD) == 0xF0) // ReactOS { RetVal = TRUE; } } /* Restore the MCR */ WRITE_PORT_UCHAR((PUCHAR)SER2_IO_o_MODEM_CONTROL, 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((PUCHAR)SER2_IO_o_SCRATCH, Byte); if (READ_PORT_UCHAR((PUCHAR)SER2_IO_i_SCRATCH) != Byte) return FALSE; } while (++Byte != 0); return TRUE; } BOOLEAN NTAPI CpDoesPortExist(IN PUCHAR Address) { UCHAR Data, Status; if (Address == Rs232ComPort[0].Address || Address == (PUCHAR)0x41) { Data = READ_PORT_UCHAR(Address); Status = READ_PORT_UCHAR(Address + 2); if ((Data & Status) == 0xFF || (Data | Status) == 0x00) return FALSE; else return TRUE; } else if (Address == Rs232ComPort[1].Address) { return (ComPortTest1(Address) || ComPortTest2(Address)); } return FALSE; } UCHAR NTAPI CpReadLsr( IN PCPPORT Port, IN UCHAR ExpectedValue) { UCHAR Lsr, Msr; SYSTEM_CONTROL_PORT_B_REGISTER SystemControl; if (Port->Address == Rs232ComPort[0].Address) { /* Read the LSR and check if the expected value is present */ if (Rs232ComPort[0].HasFifo) { Lsr = READ_PORT_UCHAR((PUCHAR)SER1_IO_i_LINE_STATUS); if (!(Lsr & ExpectedValue)) { Msr = READ_PORT_UCHAR((PUCHAR)SER1_IO_i_MODEM_STATUS); /* If the ring indicator reaches 3, we've seen this on/off twice */ Rs232ComPort[0].RingIndicator |= (Msr & SER_MSR_RI) ? 1 : 2; if (Rs232ComPort[0].RingIndicator == 3) Port->Flags |= CPPORT_FLAG_MODEM_CONTROL; } } else { Lsr = READ_PORT_UCHAR((PUCHAR)SER1_IO_i_STATUS); if (!(Lsr & ExpectedValue)) { SystemControl.Bits = READ_PORT_UCHAR((PUCHAR)PPI_IO_i_PORT_B); /* If the ring indicator reaches 3, we've seen this on/off twice */ Rs232ComPort[0].RingIndicator |= SystemControl.RingIndicator ? 1 : 2; if (Rs232ComPort[0].RingIndicator == 3) Port->Flags |= CPPORT_FLAG_MODEM_CONTROL; } } return Lsr; } else if (Port->Address == Rs232ComPort[1].Address) { /* Read the LSR and check if the expected value is present */ Lsr = READ_PORT_UCHAR((PUCHAR)SER2_IO_i_LINE_STATUS); if (!(Lsr & ExpectedValue)) { Msr = READ_PORT_UCHAR((PUCHAR)SER2_IO_i_MODEM_STATUS); /* If the indicator reaches 3, we've seen this on/off twice */ Rs232ComPort[1].RingIndicator |= (Msr & SER_MSR_RI) ? 1 : 2; if (Rs232ComPort[1].RingIndicator == 3) Port->Flags |= CPPORT_FLAG_MODEM_CONTROL; } return Lsr; } return 0; } USHORT NTAPI CpGetByte( IN PCPPORT Port, OUT PUCHAR Byte, IN BOOLEAN Wait, IN BOOLEAN Poll) { UCHAR Lsr; ULONG LimitCount = Wait ? TIMEOUT_COUNT : 1; UCHAR SuccessFlags, ErrorFlags; BOOLEAN FifoEnabled; /* Handle early read-before-init */ if (!Port->Address) return CP_GET_NODATA; if (Port->Address == Rs232ComPort[0].Address) { SuccessFlags = Rs232ComPort[0].HasFifo ? SER1_LSR_RxRDY : SER1_STATUS_RxRDY; ErrorFlags = Rs232ComPort[0].HasFifo ? (SER1_LSR_PE | SER1_LSR_OE) : (SER1_STATUS_FE | SER1_STATUS_PE | SER1_STATUS_OE); /* If "wait" mode enabled, spin many times, otherwise attempt just once */ while (LimitCount--) { /* Read LSR for data ready */ Lsr = CpReadLsr(Port, SuccessFlags); if (Lsr & SuccessFlags) { /* If an error happened, clear the byte and fail */ if (Lsr & ErrorFlags) { /* Save the last FIFO state */ FifoEnabled = Rs232ComPort[0].FifoEnabled; /* Turn off FIFO */ if (FifoEnabled) CpEnableFifo(Port->Address, FALSE); /* Clear error flag */ WRITE_PORT_UCHAR((PUCHAR)SER1_IO_o_MODE_COMMAND, SER1_COMMMAND_TxEN | SER1_COMMMAND_DTR | SER1_COMMMAND_RxEN | SER1_COMMMAND_ER | SER1_COMMMAND_RTS); /* Turn on FIFO */ if (FifoEnabled) CpEnableFifo(Port->Address, TRUE); *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 */ if (Rs232ComPort[0].HasFifo) *Byte = READ_PORT_UCHAR((PUCHAR)SER1_IO_i_RECEIVER_BUFFER); else *Byte = READ_PORT_UCHAR((PUCHAR)SER1_IO_i_DATA); /* TODO: Handle CD if port is in modem control mode */ /* Byte was read */ return CP_GET_SUCCESS; } else if (IsNekoProject && Rs232ComPort[0].HasFifo) { /* * Neko Project 21/W doesn't set RxRDY without reading any data from 0x136. * TODO: Check real hardware behavior. */ (VOID)READ_PORT_UCHAR((PUCHAR)SER1_IO_i_INTERRUPT_ID); } } /* Reset LSR, no data was found */ CpReadLsr(Port, 0); } else if (Port->Address == Rs232ComPort[1].Address) { /* If "wait" mode enabled, spin many times, otherwise attempt just once */ while (LimitCount--) { /* Read LSR for data ready */ Lsr = CpReadLsr(Port, SER2_LSR_DR); if ((Lsr & SER2_LSR_DR) == SER2_LSR_DR) { /* If an error happened, clear the byte and fail */ if (Lsr & (SER2_LSR_FE | SER2_LSR_PE | SER2_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((UCHAR)SER2_IO_i_RECEIVER_BUFFER); /* TODO: Handle CD if port is in modem control mode */ /* 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) { if (Port->Address == Rs232ComPort[0].Address) { /* TODO: Check if port is in modem control to handle CD */ if (Rs232ComPort[0].HasFifo) { while ((CpReadLsr(Port, SER1_LSR_TxRDY) & SER1_LSR_TxRDY) == 0) NOTHING; WRITE_PORT_UCHAR((PUCHAR)SER1_IO_o_TRANSMITTER_BUFFER, Byte); } else { while ((CpReadLsr(Port, SER1_STATUS_TxRDY) & SER1_STATUS_TxRDY) == 0) NOTHING; WRITE_PORT_UCHAR((PUCHAR)SER1_IO_o_DATA, Byte); } } else if (Port->Address == Rs232ComPort[1].Address) { /* TODO: Check if port is in modem control to handle CD */ while ((CpReadLsr(Port, SER2_LSR_THR_EMPTY) & SER2_LSR_THR_EMPTY) == 0) NOTHING; WRITE_PORT_UCHAR((PUCHAR)SER2_IO_o_TRANSMITTER_BUFFER, Byte); } }