mirror of
https://github.com/reactos/reactos.git
synced 2024-12-28 10:04:49 +00:00
277 lines
7.6 KiB
C
277 lines
7.6 KiB
C
/*
|
|
* PROJECT: ReactOS KDBG Kernel Debugger Terminal Driver
|
|
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
|
|
* PURPOSE: KD Terminal Management
|
|
* COPYRIGHT: Copyright 2005 Gregor Anich <blight@blight.eu.org>
|
|
* Copyright 2022-2023 Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
|
|
*/
|
|
|
|
/* INCLUDES ******************************************************************/
|
|
|
|
#include <ntoskrnl.h>
|
|
#include "kd.h"
|
|
#include "kdterminal.h"
|
|
|
|
#define KdbpGetCharKeyboard(ScanCode) KdbpTryGetCharKeyboard((ScanCode), 0)
|
|
CHAR
|
|
KdbpTryGetCharKeyboard(PULONG ScanCode, ULONG Retry);
|
|
|
|
#define KdbpGetCharSerial() KdbpTryGetCharSerial(0)
|
|
CHAR
|
|
KdbpTryGetCharSerial(
|
|
_In_ ULONG Retry);
|
|
|
|
VOID
|
|
KdbpSendCommandSerial(
|
|
_In_ PCSTR Command);
|
|
|
|
|
|
/* GLOBALS *******************************************************************/
|
|
|
|
/* KD Controlling Terminal */
|
|
ULONG KdbDebugState = 0; /* KDBG Settings (NOECHO, KDSERIAL) */
|
|
SIZE KdTermSize = {0,0};
|
|
BOOLEAN KdTermConnected = FALSE;
|
|
BOOLEAN KdTermSerial = FALSE;
|
|
BOOLEAN KdTermReportsSize = TRUE;
|
|
|
|
static CHAR KdTermNextKey = ANSI_NULL; /* 1-character input queue buffer */
|
|
|
|
|
|
/* FUNCTIONS *****************************************************************/
|
|
|
|
/**
|
|
* @brief Initializes the controlling terminal.
|
|
*
|
|
* @return
|
|
* TRUE if the controlling terminal is serial and detected
|
|
* as being connected, or FALSE if not.
|
|
**/
|
|
BOOLEAN
|
|
KdpInitTerminal(VOID)
|
|
{
|
|
/* Determine whether the controlling terminal is a serial terminal:
|
|
* serial output is enabled *and* KDSERIAL is set (i.e. user input
|
|
* through serial). */
|
|
KdTermSerial =
|
|
#if 0
|
|
// Old logic where KDSERIAL also enables serial output.
|
|
(KdbDebugState & KD_DEBUG_KDSERIAL) ||
|
|
(KdpDebugMode.Serial && !KdpDebugMode.Screen);
|
|
#else
|
|
// New logic where KDSERIAL does not necessarily enable serial output.
|
|
KdpDebugMode.Serial &&
|
|
((KdbDebugState & KD_DEBUG_KDSERIAL) || !KdpDebugMode.Screen);
|
|
#endif
|
|
|
|
/* Flush the input buffer */
|
|
KdpFlushTerminalInput();
|
|
|
|
if (KdTermSerial)
|
|
{
|
|
ULONG Length;
|
|
|
|
/* Enable line-wrap */
|
|
KdbpSendCommandSerial("\x1b[?7h");
|
|
|
|
/*
|
|
* Query terminal type.
|
|
* Historically it was done with CTRL-E ('\x05'), however nowadays
|
|
* terminals respond to it with an empty (or a user-configurable)
|
|
* string. Instead, use the VT52-compatible 'ESC Z' sequence or the
|
|
* VT100-compatible 'ESC[c' one.
|
|
*/
|
|
KdbpSendCommandSerial("\x1b[c");
|
|
KeStallExecutionProcessor(100000);
|
|
|
|
Length = 0;
|
|
for (;;)
|
|
{
|
|
/* Verify we get an answer, but don't care about it */
|
|
if (KdbpTryGetCharSerial(5000) == -1)
|
|
break;
|
|
++Length;
|
|
}
|
|
|
|
/* Terminal is connected (TRUE) or not connected (FALSE) */
|
|
KdTermConnected = (Length > 0);
|
|
}
|
|
else
|
|
{
|
|
/* Terminal is not serial, assume it's *not* connected */
|
|
KdTermConnected = FALSE;
|
|
}
|
|
return KdTermConnected;
|
|
}
|
|
|
|
BOOLEAN
|
|
KdpUpdateTerminalSize(
|
|
_Out_ PSIZE TermSize)
|
|
{
|
|
static CHAR Buffer[128];
|
|
CHAR c;
|
|
LONG NumberOfCols = -1; // Or initialize to TermSize->cx ??
|
|
LONG NumberOfRows = -1; // Or initialize to TermSize->cy ??
|
|
|
|
/* Retrieve the size of the controlling terminal only when it is serial */
|
|
if (KdTermConnected && KdTermSerial && KdTermReportsSize)
|
|
{
|
|
/* Flush the input buffer */
|
|
KdpFlushTerminalInput();
|
|
|
|
/* Try to query the terminal size. A reply looks like "\x1b[8;24;80t" */
|
|
KdTermReportsSize = FALSE;
|
|
KdbpSendCommandSerial("\x1b[18t");
|
|
KeStallExecutionProcessor(100000);
|
|
|
|
c = KdbpTryGetCharSerial(5000);
|
|
if (c == KEY_ESC)
|
|
{
|
|
c = KdbpTryGetCharSerial(5000);
|
|
if (c == '[')
|
|
{
|
|
ULONG Length = 0;
|
|
for (;;)
|
|
{
|
|
c = KdbpTryGetCharSerial(5000);
|
|
if (c == -1)
|
|
break;
|
|
|
|
Buffer[Length++] = c;
|
|
if (isalpha(c) || Length >= (sizeof(Buffer) - 1))
|
|
break;
|
|
}
|
|
Buffer[Length] = ANSI_NULL;
|
|
|
|
if (Buffer[0] == '8' && Buffer[1] == ';')
|
|
{
|
|
SIZE_T i;
|
|
for (i = 2; (i < Length) && (Buffer[i] != ';'); i++);
|
|
|
|
if (Buffer[i] == ';')
|
|
{
|
|
Buffer[i++] = ANSI_NULL;
|
|
|
|
/* Number of rows is now at Buffer + 2
|
|
* and number of columns at Buffer + i */
|
|
NumberOfRows = strtoul(Buffer + 2, NULL, 0);
|
|
NumberOfCols = strtoul(Buffer + i, NULL, 0);
|
|
KdTermReportsSize = TRUE;
|
|
}
|
|
}
|
|
}
|
|
/* Clear further characters */
|
|
while (KdbpTryGetCharSerial(5000) != -1);
|
|
}
|
|
}
|
|
|
|
if (NumberOfCols <= 0)
|
|
{
|
|
/* Set the number of columns to the default */
|
|
if (KdpDebugMode.Screen && !KdTermSerial)
|
|
NumberOfCols = (SCREEN_WIDTH / 8 /*BOOTCHAR_WIDTH*/);
|
|
else
|
|
NumberOfCols = 80;
|
|
}
|
|
if (NumberOfRows <= 0)
|
|
{
|
|
/* Set the number of rows to the default */
|
|
if (KdpDebugMode.Screen && !KdTermSerial)
|
|
NumberOfRows = (SCREEN_HEIGHT / (13 /*BOOTCHAR_HEIGHT*/ + 1));
|
|
else
|
|
NumberOfRows = 24;
|
|
}
|
|
|
|
TermSize->cx = NumberOfCols;
|
|
TermSize->cy = NumberOfRows;
|
|
|
|
// KdIoPrintf("Cols/Rows: %dx%d\n", TermSize->cx, TermSize->cy);
|
|
|
|
return KdTermReportsSize;
|
|
}
|
|
|
|
/**
|
|
* @brief Flushes terminal input (either serial or PS/2).
|
|
**/
|
|
VOID
|
|
KdpFlushTerminalInput(VOID)
|
|
{
|
|
KdTermNextKey = ANSI_NULL;
|
|
if (KdbDebugState & KD_DEBUG_KDSERIAL)
|
|
{
|
|
while (KdbpTryGetCharSerial(1) != -1);
|
|
}
|
|
else
|
|
{
|
|
ULONG ScanCode;
|
|
while (KdbpTryGetCharKeyboard(&ScanCode, 1) != -1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Reads one character from the terminal. This function returns
|
|
* a scan code even when reading is done from a serial terminal.
|
|
**/
|
|
CHAR
|
|
KdpReadTermKey(
|
|
_Out_ PULONG ScanCode)
|
|
{
|
|
CHAR Key;
|
|
|
|
*ScanCode = 0;
|
|
|
|
if (KdbDebugState & KD_DEBUG_KDSERIAL)
|
|
{
|
|
Key = (!KdTermNextKey ? KdbpGetCharSerial() : KdTermNextKey);
|
|
KdTermNextKey = ANSI_NULL;
|
|
if (Key == KEY_ESC) /* ESC */
|
|
{
|
|
Key = KdbpGetCharSerial();
|
|
if (Key == '[')
|
|
{
|
|
Key = KdbpGetCharSerial();
|
|
switch (Key)
|
|
{
|
|
case 'A':
|
|
*ScanCode = KEY_SCAN_UP;
|
|
break;
|
|
case 'B':
|
|
*ScanCode = KEY_SCAN_DOWN;
|
|
break;
|
|
case 'C':
|
|
break;
|
|
case 'D':
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Key = (!KdTermNextKey ? KdbpGetCharKeyboard(ScanCode) : KdTermNextKey);
|
|
KdTermNextKey = ANSI_NULL;
|
|
}
|
|
|
|
/* Check for return */
|
|
if (Key == '\r')
|
|
{
|
|
/*
|
|
* We might need to discard the next '\n' which most clients
|
|
* should send after \r. Wait a bit to make sure we receive it.
|
|
*/
|
|
KeStallExecutionProcessor(100000);
|
|
|
|
if (KdbDebugState & KD_DEBUG_KDSERIAL)
|
|
KdTermNextKey = KdbpTryGetCharSerial(5);
|
|
else
|
|
KdTermNextKey = KdbpTryGetCharKeyboard(ScanCode, 5);
|
|
|
|
if (KdTermNextKey == '\n' || KdTermNextKey == -1) /* \n or no response at all */
|
|
KdTermNextKey = ANSI_NULL;
|
|
}
|
|
|
|
return Key;
|
|
}
|
|
|
|
/* EOF */
|