xref: /reactos/ntoskrnl/kd/kdterminal.c (revision 3c0ba6b2)
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