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
KdpInitTerminal(VOID)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
KdpUpdateTerminalSize(_Out_ PSIZE TermSize)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
KdpFlushTerminalInput(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
KdpReadTermKey(_Out_ PULONG ScanCode)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