mirror of
https://github.com/reactos/reactos.git
synced 2024-11-01 12:26:32 +00:00
609 lines
19 KiB
C
609 lines
19 KiB
C
/*
|
|
* PROJECT: ReactOS Kernel
|
|
* LICENSE: BSD - See COPYING.ARM in the top level directory
|
|
* FILE: ntoskrnl/ex/hdlsterm.c
|
|
* PURPOSE: Headless Terminal Support
|
|
* PROGRAMMERS: ReactOS Portable Systems Group
|
|
*/
|
|
|
|
/* INCLUDES *******************************************************************/
|
|
|
|
#include <ntoskrnl.h>
|
|
#include <debug.h>
|
|
|
|
/* GLOBALS ********************************************************************/
|
|
|
|
PHEADLESS_GLOBALS HeadlessGlobals;
|
|
|
|
/* FUNCTIONS ******************************************************************/
|
|
|
|
FORCEINLINE
|
|
KIRQL
|
|
HdlspAcquireGlobalLock(VOID)
|
|
{
|
|
KIRQL OldIrql;
|
|
|
|
/* Don't acquire the lock if we are bugchecking */
|
|
if (!HeadlessGlobals->InBugCheck)
|
|
{
|
|
KeAcquireSpinLock(&HeadlessGlobals->SpinLock, &OldIrql);
|
|
}
|
|
else
|
|
{
|
|
OldIrql = 0xFF;
|
|
}
|
|
|
|
return OldIrql;
|
|
}
|
|
|
|
FORCEINLINE
|
|
VOID
|
|
HdlspReleaseGlobalLock(IN KIRQL OldIrql)
|
|
{
|
|
/* Only release the lock if we aren't bugchecking */
|
|
if (OldIrql != 0xFF)
|
|
{
|
|
KeReleaseSpinLock(&HeadlessGlobals->SpinLock, OldIrql);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(HeadlessGlobals->InBugCheck == TRUE);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
HdlspSendStringAtBaud(IN PUCHAR String)
|
|
{
|
|
/* Send every byte */
|
|
while (*String != ANSI_NULL)
|
|
{
|
|
InbvPortPutByte(HeadlessGlobals->TerminalPort, *String++);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
HdlspPutData(IN PUCHAR Data,
|
|
IN ULONG DataSize)
|
|
{
|
|
ULONG i;
|
|
for (i = 0; i < DataSize; i++)
|
|
{
|
|
InbvPortPutByte(HeadlessGlobals->TerminalPort, Data[i]);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
HdlspPutString(IN PUCHAR String)
|
|
{
|
|
PUCHAR Dest = HeadlessGlobals->TmpBuffer;
|
|
UCHAR Char = 0;
|
|
|
|
/* Scan each character */
|
|
while (*String != ANSI_NULL)
|
|
{
|
|
/* Check for rotate, send existing buffer and restart from where we are */
|
|
if (Dest >= &HeadlessGlobals->TmpBuffer[79])
|
|
{
|
|
HeadlessGlobals->TmpBuffer[79] = ANSI_NULL;
|
|
HdlspSendStringAtBaud(HeadlessGlobals->TmpBuffer);
|
|
Dest = HeadlessGlobals->TmpBuffer;
|
|
}
|
|
else
|
|
{
|
|
/* Get the current character and check for special graphical chars */
|
|
Char = *String;
|
|
if (Char & 0x80)
|
|
{
|
|
switch (Char)
|
|
{
|
|
case 0xB0: case 0xB3: case 0xBA:
|
|
Char = '|';
|
|
break;
|
|
case 0xB1: case 0xDC: case 0xDD: case 0xDE: case 0xDF:
|
|
Char = '%';
|
|
break;
|
|
case 0xB2: case 0xDB:
|
|
Char = '#';
|
|
break;
|
|
case 0xA9: case 0xAA: case 0xBB: case 0xBC: case 0xBF:
|
|
case 0xC0: case 0xC8: case 0xC9: case 0xD9: case 0xDA:
|
|
Char = '+';
|
|
break;
|
|
case 0xC4:
|
|
Char = '-';
|
|
break;
|
|
case 0xCD:
|
|
Char = '=';
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Anything else must be Unicode */
|
|
if (Char & 0x80)
|
|
{
|
|
/* Can't do Unicode yet */
|
|
UNIMPLEMENTED;
|
|
}
|
|
else
|
|
{
|
|
/* Add the modified char to the temporary buffer */
|
|
*Dest++ = Char;
|
|
}
|
|
|
|
/* Check the next char */
|
|
String++;
|
|
}
|
|
}
|
|
|
|
/* Finish and send */
|
|
*Dest = ANSI_NULL;
|
|
HdlspSendStringAtBaud(HeadlessGlobals->TmpBuffer);
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
HdlspEnableTerminal(IN BOOLEAN Enable)
|
|
{
|
|
/* Enable if requested, as long as this isn't a PCI serial port crashing */
|
|
if ((Enable) &&
|
|
!(HeadlessGlobals->TerminalEnabled) &&
|
|
!((HeadlessGlobals->IsMMIODevice) && (HeadlessGlobals->InBugCheck)))
|
|
{
|
|
/* Initialize the COM port with cportlib */
|
|
HeadlessGlobals->TerminalEnabled = InbvPortInitialize(HeadlessGlobals->TerminalBaudRate,
|
|
HeadlessGlobals->TerminalPortNumber,
|
|
HeadlessGlobals->TerminalPortAddress,
|
|
&HeadlessGlobals->TerminalPort,
|
|
HeadlessGlobals->IsMMIODevice);
|
|
if (!HeadlessGlobals->TerminalEnabled)
|
|
{
|
|
DPRINT1("Failed to initialize port through cportlib\n");
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
/* Cleanup the screen and reset the cursor */
|
|
HdlspSendStringAtBaud((PUCHAR)"\x1B[2J");
|
|
HdlspSendStringAtBaud((PUCHAR)"\x1B[H");
|
|
|
|
/* Enable FIFO */
|
|
InbvPortEnableFifo(HeadlessGlobals->TerminalPort, TRUE);
|
|
}
|
|
else if (!Enable)
|
|
{
|
|
/* Specific case when headless is being disabled */
|
|
InbvPortTerminate(HeadlessGlobals->TerminalPort);
|
|
HeadlessGlobals->TerminalPort = 0;
|
|
HeadlessGlobals->TerminalEnabled = FALSE;
|
|
}
|
|
|
|
/* All done */
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
CODE_SEG("INIT")
|
|
VOID
|
|
NTAPI
|
|
HeadlessInit(IN PLOADER_PARAMETER_BLOCK LoaderBlock)
|
|
{
|
|
PHEADLESS_LOADER_BLOCK HeadlessBlock;
|
|
|
|
/* Only initialize further if the loader found EMS enabled */
|
|
HeadlessBlock = LoaderBlock->Extension->HeadlessLoaderBlock;
|
|
if (!HeadlessBlock) return;
|
|
|
|
/* Ignore invalid EMS settings */
|
|
if ((HeadlessBlock->PortNumber > 4) && (HeadlessBlock->UsedBiosSettings)) return;
|
|
|
|
/* Allocate the global headless data */
|
|
HeadlessGlobals = ExAllocatePoolWithTag(NonPagedPool,
|
|
sizeof(*HeadlessGlobals),
|
|
'sldH');
|
|
if (!HeadlessGlobals) return;
|
|
|
|
/* Zero and copy loader data */
|
|
RtlZeroMemory(HeadlessGlobals, sizeof(*HeadlessGlobals));
|
|
HeadlessGlobals->TerminalPortNumber = HeadlessBlock->PortNumber;
|
|
HeadlessGlobals->TerminalPortAddress = HeadlessBlock->PortAddress;
|
|
HeadlessGlobals->TerminalBaudRate = HeadlessBlock->BaudRate;
|
|
HeadlessGlobals->TerminalParity = HeadlessBlock->Parity;
|
|
HeadlessGlobals->TerminalStopBits = HeadlessBlock->StopBits;
|
|
HeadlessGlobals->UsedBiosSettings = HeadlessBlock->UsedBiosSettings;
|
|
HeadlessGlobals->IsMMIODevice = HeadlessBlock->IsMMIODevice;
|
|
HeadlessGlobals->TerminalType = HeadlessBlock->TerminalType;
|
|
HeadlessGlobals->SystemGUID = HeadlessBlock->SystemGUID;
|
|
DPRINT1("EMS on Port %lu (0x%p) at %lu bps\n",
|
|
HeadlessGlobals->TerminalPortNumber,
|
|
HeadlessGlobals->TerminalPortAddress,
|
|
HeadlessGlobals->TerminalBaudRate);
|
|
|
|
/* These two are opposites of each other */
|
|
if (HeadlessGlobals->IsMMIODevice) HeadlessGlobals->IsNonLegacyDevice = TRUE;
|
|
|
|
/* Check for a PCI device, warn that this isn't supported */
|
|
if (HeadlessBlock->PciDeviceId != PCI_INVALID_VENDORID)
|
|
{
|
|
DPRINT1("PCI Serial Ports not supported\n");
|
|
}
|
|
|
|
/* Log entries are not yet supported */
|
|
DPRINT1("FIXME: No Headless logging support\n");
|
|
|
|
/* Allocate temporary buffer */
|
|
HeadlessGlobals->TmpBuffer = ExAllocatePoolWithTag(NonPagedPool, 80, 'sldH');
|
|
if (!HeadlessGlobals->TmpBuffer) return;
|
|
|
|
/* Windows seems to apply some special hacks for 9600 bps */
|
|
if (HeadlessGlobals->TerminalBaudRate == 9600)
|
|
{
|
|
DPRINT1("Please use other baud rate than 9600bps for now\n");
|
|
}
|
|
|
|
/* Enable the terminal */
|
|
HdlspEnableTerminal(TRUE);
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
HdlspDispatch(IN HEADLESS_CMD Command,
|
|
IN PVOID InputBuffer,
|
|
IN SIZE_T InputBufferSize,
|
|
OUT PVOID OutputBuffer,
|
|
OUT PSIZE_T OutputBufferSize)
|
|
{
|
|
KIRQL OldIrql;
|
|
NTSTATUS Status = STATUS_NOT_IMPLEMENTED;
|
|
PHEADLESS_RSP_QUERY_INFO HeadlessInfo;
|
|
PHEADLESS_CMD_PUT_STRING PutString;
|
|
PHEADLESS_CMD_ENABLE_TERMINAL EnableTerminal;
|
|
PHEADLESS_CMD_SET_COLOR SetColor;
|
|
PHEADLESS_CMD_POSITION_CURSOR CursorPos;
|
|
PHEADLESS_RSP_GET_BYTE GetByte;
|
|
UCHAR DataBuffer[80];
|
|
|
|
ASSERT(HeadlessGlobals != NULL);
|
|
// ASSERT(HeadlessGlobals->PageLockHandle != NULL);
|
|
|
|
/* Ignore non-reentrant commands */
|
|
if ((Command != HeadlessCmdAddLogEntry) &&
|
|
(Command != HeadlessCmdStartBugCheck) &&
|
|
(Command != HeadlessCmdSendBlueScreenData) &&
|
|
(Command != HeadlessCmdDoBugCheckProcessing))
|
|
{
|
|
OldIrql = HdlspAcquireGlobalLock();
|
|
|
|
if (HeadlessGlobals->ProcessingCmd)
|
|
{
|
|
HdlspReleaseGlobalLock(OldIrql);
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
/* Don't allow these commands next time */
|
|
HeadlessGlobals->ProcessingCmd = TRUE;
|
|
HdlspReleaseGlobalLock(OldIrql);
|
|
}
|
|
|
|
/* Handle each command */
|
|
switch (Command)
|
|
{
|
|
case HeadlessCmdEnableTerminal:
|
|
{
|
|
/* Make sure the caller passed valid data */
|
|
if (!(InputBuffer) ||
|
|
(InputBufferSize != sizeof(*EnableTerminal)))
|
|
{
|
|
DPRINT1("Invalid buffer\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
/* Go and enable it */
|
|
EnableTerminal = InputBuffer;
|
|
Status = HdlspEnableTerminal(EnableTerminal->Enable);
|
|
break;
|
|
}
|
|
|
|
case HeadlessCmdCheckForReboot:
|
|
break;
|
|
|
|
case HeadlessCmdPutString:
|
|
{
|
|
/* Validate the existence of an input buffer */
|
|
if (!InputBuffer)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
/* Terminal should be on */
|
|
if (HeadlessGlobals->TerminalEnabled)
|
|
{
|
|
/* Print each byte in the string making sure VT100 chars are used */
|
|
PutString = InputBuffer;
|
|
HdlspPutString(PutString->String);
|
|
}
|
|
|
|
/* Return success either way */
|
|
Status = STATUS_SUCCESS;
|
|
break;
|
|
}
|
|
|
|
case HeadlessCmdClearDisplay:
|
|
case HeadlessCmdClearToEndOfDisplay:
|
|
case HeadlessCmdClearToEndOfLine:
|
|
case HeadlessCmdDisplayAttributesOff:
|
|
case HeadlessCmdDisplayInverseVideo:
|
|
case HeadlessCmdSetColor:
|
|
case HeadlessCmdPositionCursor:
|
|
{
|
|
/* By default return success */
|
|
Status = STATUS_SUCCESS;
|
|
|
|
/* Send the VT100 commands only if the terminal is enabled */
|
|
if (HeadlessGlobals->TerminalEnabled)
|
|
{
|
|
PUCHAR CommandStr = NULL;
|
|
|
|
if (Command == HeadlessCmdClearDisplay)
|
|
CommandStr = (PUCHAR)"\x1B[2J";
|
|
else if (Command == HeadlessCmdClearToEndOfDisplay)
|
|
CommandStr = (PUCHAR)"\x1B[0J";
|
|
else if (Command == HeadlessCmdClearToEndOfLine)
|
|
CommandStr = (PUCHAR)"\x1B[0K";
|
|
else if (Command == HeadlessCmdDisplayAttributesOff)
|
|
CommandStr = (PUCHAR)"\x1B[0m";
|
|
else if (Command == HeadlessCmdDisplayInverseVideo)
|
|
CommandStr = (PUCHAR)"\x1B[7m";
|
|
else if (Command == HeadlessCmdSetColor)
|
|
{
|
|
/* Make sure the caller passed valid data */
|
|
if (!InputBuffer ||
|
|
(InputBufferSize != sizeof(*SetColor)))
|
|
{
|
|
DPRINT1("Invalid buffer\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
SetColor = InputBuffer;
|
|
Status = RtlStringCbPrintfA((PCHAR)DataBuffer, sizeof(DataBuffer),
|
|
"\x1B[%d;%dm",
|
|
SetColor->BkgdColor,
|
|
SetColor->TextColor);
|
|
if (!NT_SUCCESS(Status)) break;
|
|
|
|
CommandStr = DataBuffer;
|
|
}
|
|
else // if (Command == HeadlessCmdPositionCursor)
|
|
{
|
|
/* Make sure the caller passed valid data */
|
|
if (!InputBuffer ||
|
|
(InputBufferSize != sizeof(*CursorPos)))
|
|
{
|
|
DPRINT1("Invalid buffer\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
CursorPos = InputBuffer;
|
|
/* Cursor position is 1-based */
|
|
Status = RtlStringCbPrintfA((PCHAR)DataBuffer, sizeof(DataBuffer),
|
|
"\x1B[%d;%dH",
|
|
CursorPos->CursorRow + 1,
|
|
CursorPos->CursorCol + 1);
|
|
if (!NT_SUCCESS(Status)) break;
|
|
|
|
CommandStr = DataBuffer;
|
|
}
|
|
|
|
/* Send the command */
|
|
HdlspSendStringAtBaud(CommandStr);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case HeadlessCmdTerminalPoll:
|
|
break;
|
|
|
|
case HeadlessCmdGetByte:
|
|
{
|
|
/* Make sure the caller passed valid data */
|
|
if (!(OutputBuffer) ||
|
|
!(OutputBufferSize) ||
|
|
(*OutputBufferSize < sizeof(*GetByte)))
|
|
{
|
|
DPRINT1("Invalid buffer\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
/* Make sure the terminal is enabled */
|
|
GetByte = OutputBuffer;
|
|
if (HeadlessGlobals->TerminalEnabled)
|
|
{
|
|
/* Poll if something is on the wire */
|
|
if (InbvPortPollOnly(HeadlessGlobals->TerminalPort))
|
|
{
|
|
/* If so, read it */
|
|
InbvPortGetByte(HeadlessGlobals->TerminalPort,
|
|
&GetByte->Value);
|
|
}
|
|
else
|
|
{
|
|
/* Nothing is there, return 0 */
|
|
GetByte->Value = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise return nothing */
|
|
GetByte->Value = 0;
|
|
}
|
|
|
|
/* Return success either way */
|
|
Status = STATUS_SUCCESS;
|
|
break;
|
|
}
|
|
|
|
case HeadlessCmdGetLine:
|
|
break;
|
|
case HeadlessCmdStartBugCheck:
|
|
break;
|
|
case HeadlessCmdDoBugCheckProcessing:
|
|
break;
|
|
|
|
case HeadlessCmdQueryInformation:
|
|
{
|
|
/* Make sure the caller passed valid data */
|
|
if (!(OutputBuffer) ||
|
|
!(OutputBufferSize) ||
|
|
(*OutputBufferSize < sizeof(*HeadlessInfo)))
|
|
{
|
|
DPRINT1("Invalid buffer\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
/* If we got here, headless is enabled -- we know this much */
|
|
HeadlessInfo = OutputBuffer;
|
|
HeadlessInfo->PortType = HeadlessSerialPort;
|
|
HeadlessInfo->Serial.TerminalAttached = TRUE;
|
|
HeadlessInfo->Serial.UsedBiosSettings = HeadlessGlobals->UsedBiosSettings != 0;
|
|
HeadlessInfo->Serial.TerminalBaudRate = HeadlessGlobals->TerminalBaudRate;
|
|
HeadlessInfo->Serial.TerminalType = HeadlessGlobals->TerminalType;
|
|
|
|
/* Now check on what port/baud it's enabled on */
|
|
if ((HeadlessGlobals->TerminalPortNumber >= 1) ||
|
|
(HeadlessGlobals->UsedBiosSettings))
|
|
{
|
|
/* Get the EMS information */
|
|
HeadlessInfo->Serial.TerminalPort = HeadlessGlobals->
|
|
TerminalPortNumber;
|
|
HeadlessInfo->Serial.TerminalPortBaseAddress = HeadlessGlobals->
|
|
TerminalPortAddress;
|
|
}
|
|
else
|
|
{
|
|
/* We don't know for sure */
|
|
HeadlessInfo->Serial.TerminalPort = SerialPortUndefined;
|
|
HeadlessInfo->Serial.TerminalPortBaseAddress = 0;
|
|
}
|
|
|
|
/* All done */
|
|
Status = STATUS_SUCCESS;
|
|
break;
|
|
}
|
|
|
|
case HeadlessCmdAddLogEntry:
|
|
break;
|
|
case HeadlessCmdDisplayLog:
|
|
break;
|
|
|
|
case HeadlessCmdSetBlueScreenData:
|
|
{
|
|
/* Validate the existence of an input buffer */
|
|
if (!InputBuffer)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
/* Lie so that we can get Hdl bringup a little bit further */
|
|
UNIMPLEMENTED;
|
|
Status = STATUS_SUCCESS;
|
|
break;
|
|
}
|
|
|
|
case HeadlessCmdSendBlueScreenData:
|
|
break;
|
|
case HeadlessCmdQueryGUID:
|
|
break;
|
|
|
|
case HeadlessCmdPutData:
|
|
{
|
|
/* Validate the existence of an input buffer */
|
|
if (!(InputBuffer) || !(InputBufferSize))
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
/* Terminal should be on */
|
|
if (HeadlessGlobals->TerminalEnabled)
|
|
{
|
|
/* Print each byte in the string making sure VT100 chars are used */
|
|
PutString = InputBuffer;
|
|
HdlspPutData(PutString->String, InputBufferSize);
|
|
}
|
|
|
|
/* Return success either way */
|
|
Status = STATUS_SUCCESS;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Unset processing state */
|
|
if ((Command != HeadlessCmdAddLogEntry) &&
|
|
(Command != HeadlessCmdStartBugCheck) &&
|
|
(Command != HeadlessCmdSendBlueScreenData) &&
|
|
(Command != HeadlessCmdDoBugCheckProcessing))
|
|
{
|
|
ASSERT(HeadlessGlobals->ProcessingCmd == TRUE);
|
|
HeadlessGlobals->ProcessingCmd = FALSE;
|
|
}
|
|
|
|
/* All done */
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
HeadlessDispatch(IN HEADLESS_CMD Command,
|
|
IN PVOID InputBuffer,
|
|
IN SIZE_T InputBufferSize,
|
|
OUT PVOID OutputBuffer,
|
|
OUT PSIZE_T OutputBufferSize)
|
|
{
|
|
/* Check for stubs that will expect something even with headless off */
|
|
if (!HeadlessGlobals)
|
|
{
|
|
/* Don't allow the SAC to connect */
|
|
if (Command == HeadlessCmdEnableTerminal) return STATUS_UNSUCCESSFUL;
|
|
|
|
/* Send bogus reply */
|
|
if ((Command == HeadlessCmdQueryInformation) ||
|
|
(Command == HeadlessCmdGetByte) ||
|
|
(Command == HeadlessCmdGetLine) ||
|
|
(Command == HeadlessCmdCheckForReboot) ||
|
|
(Command == HeadlessCmdTerminalPoll))
|
|
{
|
|
if (!(OutputBuffer) || !(OutputBufferSize))
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
RtlZeroMemory(OutputBuffer, *OutputBufferSize);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/* Do the real work */
|
|
return HdlspDispatch(Command,
|
|
InputBuffer,
|
|
InputBufferSize,
|
|
OutputBuffer,
|
|
OutputBufferSize);
|
|
}
|
|
|
|
/* EOF */
|