1 /* 2 * PROJECT: ReactOS KDBG Kernel Debugger Terminal Driver 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: KD Terminal Management 5 * COPYRIGHT: Copyright 2005 Gregor Anich <blight@blight.eu.org> 6 * Copyright 2022-2023 Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org> 7 */ 8 9 /* INCLUDES ******************************************************************/ 10 11 #include <ntoskrnl.h> 12 #include "kd.h" 13 #include "kdterminal.h" 14 15 #define KdbpGetCharKeyboard(ScanCode) KdbpTryGetCharKeyboard((ScanCode), 0) 16 CHAR 17 KdbpTryGetCharKeyboard(PULONG ScanCode, ULONG Retry); 18 19 #define KdbpGetCharSerial() KdbpTryGetCharSerial(0) 20 CHAR 21 KdbpTryGetCharSerial( 22 _In_ ULONG Retry); 23 24 VOID 25 KdbpSendCommandSerial( 26 _In_ PCSTR Command); 27 28 29 /* GLOBALS *******************************************************************/ 30 31 /* KD Controlling Terminal */ 32 ULONG KdbDebugState = 0; /* KDBG Settings (NOECHO, KDSERIAL) */ 33 SIZE KdTermSize = {0,0}; 34 BOOLEAN KdTermConnected = FALSE; 35 BOOLEAN KdTermSerial = FALSE; 36 BOOLEAN KdTermReportsSize = TRUE; 37 38 static CHAR KdTermNextKey = ANSI_NULL; /* 1-character input queue buffer */ 39 40 41 /* FUNCTIONS *****************************************************************/ 42 43 /** 44 * @brief Initializes the controlling terminal. 45 * 46 * @return 47 * TRUE if the controlling terminal is serial and detected 48 * as being connected, or FALSE if not. 49 **/ 50 BOOLEAN 51 KdpInitTerminal(VOID) 52 { 53 /* Determine whether the controlling terminal is a serial terminal: 54 * serial output is enabled *and* KDSERIAL is set (i.e. user input 55 * through serial). */ 56 KdTermSerial = 57 #if 0 58 // Old logic where KDSERIAL also enables serial output. 59 (KdbDebugState & KD_DEBUG_KDSERIAL) || 60 (KdpDebugMode.Serial && !KdpDebugMode.Screen); 61 #else 62 // New logic where KDSERIAL does not necessarily enable serial output. 63 KdpDebugMode.Serial && 64 ((KdbDebugState & KD_DEBUG_KDSERIAL) || !KdpDebugMode.Screen); 65 #endif 66 67 /* Flush the input buffer */ 68 KdpFlushTerminalInput(); 69 70 if (KdTermSerial) 71 { 72 ULONG Length; 73 74 /* Enable line-wrap */ 75 KdbpSendCommandSerial("\x1b[?7h"); 76 77 /* 78 * Query terminal type. 79 * Historically it was done with CTRL-E ('\x05'), however nowadays 80 * terminals respond to it with an empty (or a user-configurable) 81 * string. Instead, use the VT52-compatible 'ESC Z' sequence or the 82 * VT100-compatible 'ESC[c' one. 83 */ 84 KdbpSendCommandSerial("\x1b[c"); 85 KeStallExecutionProcessor(100000); 86 87 Length = 0; 88 for (;;) 89 { 90 /* Verify we get an answer, but don't care about it */ 91 if (KdbpTryGetCharSerial(5000) == -1) 92 break; 93 ++Length; 94 } 95 96 /* Terminal is connected (TRUE) or not connected (FALSE) */ 97 KdTermConnected = (Length > 0); 98 } 99 else 100 { 101 /* Terminal is not serial, assume it's *not* connected */ 102 KdTermConnected = FALSE; 103 } 104 return KdTermConnected; 105 } 106 107 BOOLEAN 108 KdpUpdateTerminalSize( 109 _Out_ PSIZE TermSize) 110 { 111 static CHAR Buffer[128]; 112 CHAR c; 113 LONG NumberOfCols = -1; // Or initialize to TermSize->cx ?? 114 LONG NumberOfRows = -1; // Or initialize to TermSize->cy ?? 115 116 /* Retrieve the size of the controlling terminal only when it is serial */ 117 if (KdTermConnected && KdTermSerial && KdTermReportsSize) 118 { 119 /* Flush the input buffer */ 120 KdpFlushTerminalInput(); 121 122 /* Try to query the terminal size. A reply looks like "\x1b[8;24;80t" */ 123 KdTermReportsSize = FALSE; 124 KdbpSendCommandSerial("\x1b[18t"); 125 KeStallExecutionProcessor(100000); 126 127 c = KdbpTryGetCharSerial(5000); 128 if (c == KEY_ESC) 129 { 130 c = KdbpTryGetCharSerial(5000); 131 if (c == '[') 132 { 133 ULONG Length = 0; 134 for (;;) 135 { 136 c = KdbpTryGetCharSerial(5000); 137 if (c == -1) 138 break; 139 140 Buffer[Length++] = c; 141 if (isalpha(c) || Length >= (sizeof(Buffer) - 1)) 142 break; 143 } 144 Buffer[Length] = ANSI_NULL; 145 146 if (Buffer[0] == '8' && Buffer[1] == ';') 147 { 148 SIZE_T i; 149 for (i = 2; (i < Length) && (Buffer[i] != ';'); i++); 150 151 if (Buffer[i] == ';') 152 { 153 Buffer[i++] = ANSI_NULL; 154 155 /* Number of rows is now at Buffer + 2 156 * and number of columns at Buffer + i */ 157 NumberOfRows = strtoul(Buffer + 2, NULL, 0); 158 NumberOfCols = strtoul(Buffer + i, NULL, 0); 159 KdTermReportsSize = TRUE; 160 } 161 } 162 } 163 /* Clear further characters */ 164 while (KdbpTryGetCharSerial(5000) != -1); 165 } 166 } 167 168 if (NumberOfCols <= 0) 169 { 170 /* Set the number of columns to the default */ 171 if (KdpDebugMode.Screen && !KdTermSerial) 172 NumberOfCols = (SCREEN_WIDTH / 8 /*BOOTCHAR_WIDTH*/); 173 else 174 NumberOfCols = 80; 175 } 176 if (NumberOfRows <= 0) 177 { 178 /* Set the number of rows to the default */ 179 if (KdpDebugMode.Screen && !KdTermSerial) 180 NumberOfRows = (SCREEN_HEIGHT / (13 /*BOOTCHAR_HEIGHT*/ + 1)); 181 else 182 NumberOfRows = 24; 183 } 184 185 TermSize->cx = NumberOfCols; 186 TermSize->cy = NumberOfRows; 187 188 // KdIoPrintf("Cols/Rows: %dx%d\n", TermSize->cx, TermSize->cy); 189 190 return KdTermReportsSize; 191 } 192 193 /** 194 * @brief Flushes terminal input (either serial or PS/2). 195 **/ 196 VOID 197 KdpFlushTerminalInput(VOID) 198 { 199 KdTermNextKey = ANSI_NULL; 200 if (KdbDebugState & KD_DEBUG_KDSERIAL) 201 { 202 while (KdbpTryGetCharSerial(1) != -1); 203 } 204 else 205 { 206 ULONG ScanCode; 207 while (KdbpTryGetCharKeyboard(&ScanCode, 1) != -1); 208 } 209 } 210 211 /** 212 * @brief 213 * Reads one character from the terminal. This function returns 214 * a scan code even when reading is done from a serial terminal. 215 **/ 216 CHAR 217 KdpReadTermKey( 218 _Out_ PULONG ScanCode) 219 { 220 CHAR Key; 221 222 *ScanCode = 0; 223 224 if (KdbDebugState & KD_DEBUG_KDSERIAL) 225 { 226 Key = (!KdTermNextKey ? KdbpGetCharSerial() : KdTermNextKey); 227 KdTermNextKey = ANSI_NULL; 228 if (Key == KEY_ESC) /* ESC */ 229 { 230 Key = KdbpGetCharSerial(); 231 if (Key == '[') 232 { 233 Key = KdbpGetCharSerial(); 234 switch (Key) 235 { 236 case 'A': 237 *ScanCode = KEY_SCAN_UP; 238 break; 239 case 'B': 240 *ScanCode = KEY_SCAN_DOWN; 241 break; 242 case 'C': 243 break; 244 case 'D': 245 break; 246 } 247 } 248 } 249 } 250 else 251 { 252 Key = (!KdTermNextKey ? KdbpGetCharKeyboard(ScanCode) : KdTermNextKey); 253 KdTermNextKey = ANSI_NULL; 254 } 255 256 /* Check for return */ 257 if (Key == '\r') 258 { 259 /* 260 * We might need to discard the next '\n' which most clients 261 * should send after \r. Wait a bit to make sure we receive it. 262 */ 263 KeStallExecutionProcessor(100000); 264 265 if (KdbDebugState & KD_DEBUG_KDSERIAL) 266 KdTermNextKey = KdbpTryGetCharSerial(5); 267 else 268 KdTermNextKey = KdbpTryGetCharKeyboard(ScanCode, 5); 269 270 if (KdTermNextKey == '\n' || KdTermNextKey == -1) /* \n or no response at all */ 271 KdTermNextKey = ANSI_NULL; 272 } 273 274 return Key; 275 } 276 277 /* EOF */ 278