xref: /reactos/ntoskrnl/ex/hdlsterm.c (revision 3abb2108)
1 /*
2  * PROJECT:         ReactOS Kernel
3  * LICENSE:         BSD - See COPYING.ARM in the top level directory
4  * FILE:            ntoskrnl/ex/hdlsterm.c
5  * PURPOSE:         Headless Terminal Support
6  * PROGRAMMERS:     ReactOS Portable Systems Group
7  */
8 
9 /* INCLUDES *******************************************************************/
10 
11 #include <ntoskrnl.h>
12 #include <debug.h>
13 
14 /* GLOBALS ********************************************************************/
15 
16 PHEADLESS_GLOBALS HeadlessGlobals;
17 
18 /* FUNCTIONS ******************************************************************/
19 
20 FORCEINLINE
21 KIRQL
HdlspAcquireGlobalLock(VOID)22 HdlspAcquireGlobalLock(VOID)
23 {
24     KIRQL OldIrql;
25 
26     /* Don't acquire the lock if we are bugchecking */
27     if (!HeadlessGlobals->InBugCheck)
28     {
29         KeAcquireSpinLock(&HeadlessGlobals->SpinLock, &OldIrql);
30     }
31     else
32     {
33         OldIrql = 0xFF;
34     }
35 
36     return OldIrql;
37 }
38 
39 FORCEINLINE
40 VOID
HdlspReleaseGlobalLock(IN KIRQL OldIrql)41 HdlspReleaseGlobalLock(IN KIRQL OldIrql)
42 {
43     /* Only release the lock if we aren't bugchecking */
44     if (OldIrql != 0xFF)
45     {
46         KeReleaseSpinLock(&HeadlessGlobals->SpinLock, OldIrql);
47     }
48     else
49     {
50         ASSERT(HeadlessGlobals->InBugCheck == TRUE);
51     }
52 }
53 
54 VOID
55 NTAPI
HdlspSendStringAtBaud(IN PUCHAR String)56 HdlspSendStringAtBaud(IN PUCHAR String)
57 {
58     /* Send every byte */
59     while (*String != ANSI_NULL)
60     {
61         InbvPortPutByte(HeadlessGlobals->TerminalPort, *String++);
62     }
63 }
64 
65 VOID
66 NTAPI
HdlspPutData(IN PUCHAR Data,IN ULONG DataSize)67 HdlspPutData(IN PUCHAR Data,
68              IN ULONG DataSize)
69 {
70     ULONG i;
71     for (i = 0; i < DataSize; i++)
72     {
73         InbvPortPutByte(HeadlessGlobals->TerminalPort, Data[i]);
74     }
75 }
76 
77 VOID
78 NTAPI
HdlspPutString(IN PUCHAR String)79 HdlspPutString(IN PUCHAR String)
80 {
81     PUCHAR Dest = HeadlessGlobals->TmpBuffer;
82     UCHAR Char = 0;
83 
84     /* Scan each character */
85     while (*String != ANSI_NULL)
86     {
87         /* Check for rotate, send existing buffer and restart from where we are */
88         if (Dest >= &HeadlessGlobals->TmpBuffer[79])
89         {
90             HeadlessGlobals->TmpBuffer[79] = ANSI_NULL;
91             HdlspSendStringAtBaud(HeadlessGlobals->TmpBuffer);
92             Dest = HeadlessGlobals->TmpBuffer;
93         }
94         else
95         {
96             /* Get the current character and check for special graphical chars */
97             Char = *String;
98             if (Char & 0x80)
99             {
100                 switch (Char)
101                 {
102                     case 0xB0: case 0xB3: case 0xBA:
103                         Char = '|';
104                         break;
105                     case 0xB1: case 0xDC: case 0xDD: case 0xDE: case 0xDF:
106                         Char = '%';
107                         break;
108                     case 0xB2: case 0xDB:
109                         Char = '#';
110                         break;
111                     case 0xA9: case 0xAA: case 0xBB: case 0xBC: case 0xBF:
112                     case 0xC0: case 0xC8: case 0xC9: case 0xD9: case 0xDA:
113                         Char = '+';
114                         break;
115                     case 0xC4:
116                         Char = '-';
117                         break;
118                     case 0xCD:
119                         Char = '=';
120                         break;
121                 }
122             }
123 
124             /* Anything else must be Unicode */
125             if (Char & 0x80)
126             {
127                 /* Can't do Unicode yet */
128                 UNIMPLEMENTED;
129             }
130             else
131             {
132                 /* Add the modified char to the temporary buffer */
133                 *Dest++ = Char;
134             }
135 
136             /* Check the next char */
137             String++;
138         }
139     }
140 
141     /* Finish and send */
142     *Dest = ANSI_NULL;
143     HdlspSendStringAtBaud(HeadlessGlobals->TmpBuffer);
144 }
145 
146 NTSTATUS
147 NTAPI
HdlspEnableTerminal(IN BOOLEAN Enable)148 HdlspEnableTerminal(IN BOOLEAN Enable)
149 {
150     /* Enable if requested, as long as this isn't a PCI serial port crashing */
151     if ((Enable) &&
152         !(HeadlessGlobals->TerminalEnabled) &&
153         !((HeadlessGlobals->IsMMIODevice) && (HeadlessGlobals->InBugCheck)))
154     {
155         /* Initialize the COM port with cportlib */
156         HeadlessGlobals->TerminalEnabled = InbvPortInitialize(HeadlessGlobals->TerminalBaudRate,
157                                                               HeadlessGlobals->TerminalPortNumber,
158                                                               HeadlessGlobals->TerminalPortAddress,
159                                                               &HeadlessGlobals->TerminalPort,
160                                                               HeadlessGlobals->IsMMIODevice);
161         if (!HeadlessGlobals->TerminalEnabled)
162         {
163             DPRINT1("Failed to initialize port through cportlib\n");
164             return STATUS_UNSUCCESSFUL;
165         }
166 
167         /* Cleanup the screen and reset the cursor */
168         HdlspSendStringAtBaud((PUCHAR)"\x1B[2J");
169         HdlspSendStringAtBaud((PUCHAR)"\x1B[H");
170 
171         /* Enable FIFO */
172         InbvPortEnableFifo(HeadlessGlobals->TerminalPort, TRUE);
173     }
174     else if (!Enable)
175     {
176         /* Specific case when headless is being disabled */
177         InbvPortTerminate(HeadlessGlobals->TerminalPort);
178         HeadlessGlobals->TerminalPort = 0;
179         HeadlessGlobals->TerminalEnabled = FALSE;
180     }
181 
182     /* All done */
183     return STATUS_SUCCESS;
184 }
185 
186 CODE_SEG("INIT")
187 VOID
188 NTAPI
HeadlessInit(IN PLOADER_PARAMETER_BLOCK LoaderBlock)189 HeadlessInit(IN PLOADER_PARAMETER_BLOCK LoaderBlock)
190 {
191     PHEADLESS_LOADER_BLOCK HeadlessBlock;
192 
193     /* Only initialize further if the loader found EMS enabled */
194     HeadlessBlock = LoaderBlock->Extension->HeadlessLoaderBlock;
195     if (!HeadlessBlock) return;
196 
197     /* Ignore invalid EMS settings */
198     if ((HeadlessBlock->PortNumber > 4) && (HeadlessBlock->UsedBiosSettings)) return;
199 
200     /* Allocate the global headless data */
201     HeadlessGlobals = ExAllocatePoolWithTag(NonPagedPool,
202                                             sizeof(*HeadlessGlobals),
203                                             'sldH');
204     if (!HeadlessGlobals) return;
205 
206     /* Zero and copy loader data */
207     RtlZeroMemory(HeadlessGlobals, sizeof(*HeadlessGlobals));
208     HeadlessGlobals->TerminalPortNumber = HeadlessBlock->PortNumber;
209     HeadlessGlobals->TerminalPortAddress = HeadlessBlock->PortAddress;
210     HeadlessGlobals->TerminalBaudRate = HeadlessBlock->BaudRate;
211     HeadlessGlobals->TerminalParity = HeadlessBlock->Parity;
212     HeadlessGlobals->TerminalStopBits = HeadlessBlock->StopBits;
213     HeadlessGlobals->UsedBiosSettings = HeadlessBlock->UsedBiosSettings;
214     HeadlessGlobals->IsMMIODevice = HeadlessBlock->IsMMIODevice;
215     HeadlessGlobals->TerminalType = HeadlessBlock->TerminalType;
216     HeadlessGlobals->SystemGUID = HeadlessBlock->SystemGUID;
217     DPRINT1("EMS on Port %lu (0x%p) at %lu bps\n",
218              HeadlessGlobals->TerminalPortNumber,
219              HeadlessGlobals->TerminalPortAddress,
220              HeadlessGlobals->TerminalBaudRate);
221 
222     /* These two are opposites of each other */
223     if (HeadlessGlobals->IsMMIODevice) HeadlessGlobals->IsNonLegacyDevice = TRUE;
224 
225     /* Check for a PCI device, warn that this isn't supported */
226     if (HeadlessBlock->PciDeviceId != PCI_INVALID_VENDORID)
227     {
228         DPRINT1("PCI Serial Ports not supported\n");
229     }
230 
231     /* Log entries are not yet supported */
232     DPRINT1("FIXME: No Headless logging support\n");
233 
234     /* Allocate temporary buffer */
235     HeadlessGlobals->TmpBuffer = ExAllocatePoolWithTag(NonPagedPool, 80, 'sldH');
236     if (!HeadlessGlobals->TmpBuffer) return;
237 
238     /* Windows seems to apply some special hacks for 9600 bps */
239     if (HeadlessGlobals->TerminalBaudRate == 9600)
240     {
241         DPRINT1("Please use other baud rate than 9600bps for now\n");
242     }
243 
244     /* Enable the terminal */
245     HdlspEnableTerminal(TRUE);
246 }
247 
248 NTSTATUS
249 NTAPI
HdlspDispatch(IN HEADLESS_CMD Command,IN PVOID InputBuffer,IN SIZE_T InputBufferSize,OUT PVOID OutputBuffer,OUT PSIZE_T OutputBufferSize)250 HdlspDispatch(IN HEADLESS_CMD Command,
251               IN PVOID InputBuffer,
252               IN SIZE_T InputBufferSize,
253               OUT PVOID OutputBuffer,
254               OUT PSIZE_T OutputBufferSize)
255 {
256     KIRQL OldIrql;
257     NTSTATUS Status = STATUS_NOT_IMPLEMENTED;
258     PHEADLESS_RSP_QUERY_INFO HeadlessInfo;
259     PHEADLESS_CMD_PUT_STRING PutString;
260     PHEADLESS_CMD_ENABLE_TERMINAL EnableTerminal;
261     PHEADLESS_CMD_SET_COLOR SetColor;
262     PHEADLESS_CMD_POSITION_CURSOR CursorPos;
263     PHEADLESS_RSP_GET_BYTE GetByte;
264     UCHAR DataBuffer[80];
265 
266     ASSERT(HeadlessGlobals != NULL);
267     // ASSERT(HeadlessGlobals->PageLockHandle != NULL);
268 
269     /* Ignore non-reentrant commands */
270     if ((Command != HeadlessCmdAddLogEntry) &&
271         (Command != HeadlessCmdStartBugCheck) &&
272         (Command != HeadlessCmdSendBlueScreenData) &&
273         (Command != HeadlessCmdDoBugCheckProcessing))
274     {
275         OldIrql = HdlspAcquireGlobalLock();
276 
277         if (HeadlessGlobals->ProcessingCmd)
278         {
279             HdlspReleaseGlobalLock(OldIrql);
280             return STATUS_UNSUCCESSFUL;
281         }
282 
283         /* Don't allow these commands next time */
284         HeadlessGlobals->ProcessingCmd = TRUE;
285         HdlspReleaseGlobalLock(OldIrql);
286     }
287 
288     /* Handle each command */
289     switch (Command)
290     {
291         case HeadlessCmdEnableTerminal:
292         {
293             /* Make sure the caller passed valid data */
294             if (!(InputBuffer) ||
295                 (InputBufferSize != sizeof(*EnableTerminal)))
296             {
297                 DPRINT1("Invalid buffer\n");
298                 Status = STATUS_INVALID_PARAMETER;
299                 break;
300             }
301 
302             /* Go and enable it */
303             EnableTerminal = InputBuffer;
304             Status = HdlspEnableTerminal(EnableTerminal->Enable);
305             break;
306         }
307 
308         case HeadlessCmdCheckForReboot:
309             break;
310 
311         case HeadlessCmdPutString:
312         {
313             /* Validate the existence of an input buffer */
314             if (!InputBuffer)
315             {
316                 Status = STATUS_INVALID_PARAMETER;
317                 break;
318             }
319 
320             /* Terminal should be on */
321             if (HeadlessGlobals->TerminalEnabled)
322             {
323                 /* Print each byte in the string making sure VT100 chars are used */
324                 PutString = InputBuffer;
325                 HdlspPutString(PutString->String);
326             }
327 
328             /* Return success either way */
329             Status = STATUS_SUCCESS;
330             break;
331         }
332 
333         case HeadlessCmdClearDisplay:
334         case HeadlessCmdClearToEndOfDisplay:
335         case HeadlessCmdClearToEndOfLine:
336         case HeadlessCmdDisplayAttributesOff:
337         case HeadlessCmdDisplayInverseVideo:
338         case HeadlessCmdSetColor:
339         case HeadlessCmdPositionCursor:
340         {
341             /* By default return success */
342             Status = STATUS_SUCCESS;
343 
344             /* Send the VT100 commands only if the terminal is enabled */
345             if (HeadlessGlobals->TerminalEnabled)
346             {
347                 PUCHAR CommandStr = NULL;
348 
349                 if (Command == HeadlessCmdClearDisplay)
350                     CommandStr = (PUCHAR)"\x1B[2J";
351                 else if (Command == HeadlessCmdClearToEndOfDisplay)
352                     CommandStr = (PUCHAR)"\x1B[0J";
353                 else if (Command == HeadlessCmdClearToEndOfLine)
354                     CommandStr = (PUCHAR)"\x1B[0K";
355                 else if (Command == HeadlessCmdDisplayAttributesOff)
356                     CommandStr = (PUCHAR)"\x1B[0m";
357                 else if (Command == HeadlessCmdDisplayInverseVideo)
358                     CommandStr = (PUCHAR)"\x1B[7m";
359                 else if (Command == HeadlessCmdSetColor)
360                 {
361                     /* Make sure the caller passed valid data */
362                     if (!InputBuffer ||
363                         (InputBufferSize != sizeof(*SetColor)))
364                     {
365                         DPRINT1("Invalid buffer\n");
366                         Status = STATUS_INVALID_PARAMETER;
367                         break;
368                     }
369 
370                     SetColor = InputBuffer;
371                     Status = RtlStringCbPrintfA((PCHAR)DataBuffer, sizeof(DataBuffer),
372                                                 "\x1B[%d;%dm",
373                                                 SetColor->BkgdColor,
374                                                 SetColor->TextColor);
375                     if (!NT_SUCCESS(Status)) break;
376 
377                     CommandStr = DataBuffer;
378                 }
379                 else // if (Command == HeadlessCmdPositionCursor)
380                 {
381                     /* Make sure the caller passed valid data */
382                     if (!InputBuffer ||
383                         (InputBufferSize != sizeof(*CursorPos)))
384                     {
385                         DPRINT1("Invalid buffer\n");
386                         Status = STATUS_INVALID_PARAMETER;
387                         break;
388                     }
389 
390                     CursorPos = InputBuffer;
391                     /* Cursor position is 1-based */
392                     Status = RtlStringCbPrintfA((PCHAR)DataBuffer, sizeof(DataBuffer),
393                                                 "\x1B[%d;%dH",
394                                                 CursorPos->CursorRow + 1,
395                                                 CursorPos->CursorCol + 1);
396                     if (!NT_SUCCESS(Status)) break;
397 
398                     CommandStr = DataBuffer;
399                 }
400 
401                 /* Send the command */
402                 HdlspSendStringAtBaud(CommandStr);
403             }
404 
405             break;
406         }
407 
408         case HeadlessCmdTerminalPoll:
409             break;
410 
411         case HeadlessCmdGetByte:
412         {
413             /* Make sure the caller passed valid data */
414             if (!(OutputBuffer) ||
415                 !(OutputBufferSize) ||
416                 (*OutputBufferSize < sizeof(*GetByte)))
417             {
418                 DPRINT1("Invalid buffer\n");
419                 Status = STATUS_INVALID_PARAMETER;
420                 break;
421             }
422 
423             /* Make sure the terminal is enabled */
424             GetByte = OutputBuffer;
425             if (HeadlessGlobals->TerminalEnabled)
426             {
427                 /* Poll if something is on the wire */
428                 if (InbvPortPollOnly(HeadlessGlobals->TerminalPort))
429                 {
430                     /* If so, read it */
431                     InbvPortGetByte(HeadlessGlobals->TerminalPort,
432                                     &GetByte->Value);
433                 }
434                 else
435                 {
436                     /* Nothing is there, return 0 */
437                     GetByte->Value = 0;
438                 }
439             }
440             else
441             {
442                 /* Otherwise return nothing */
443                 GetByte->Value = 0;
444             }
445 
446             /* Return success either way */
447             Status = STATUS_SUCCESS;
448             break;
449         }
450 
451         case HeadlessCmdGetLine:
452             break;
453 
454         case HeadlessCmdStartBugCheck:
455         {
456             HeadlessGlobals->InBugCheck = TRUE;
457             HeadlessGlobals->ProcessingCmd = FALSE;
458             Status = STATUS_SUCCESS;
459             break;
460         }
461 
462         case HeadlessCmdDoBugCheckProcessing:
463             break;
464 
465         case HeadlessCmdQueryInformation:
466         {
467             /* Make sure the caller passed valid data */
468             if (!(OutputBuffer) ||
469                 !(OutputBufferSize) ||
470                 (*OutputBufferSize < sizeof(*HeadlessInfo)))
471             {
472                 DPRINT1("Invalid buffer\n");
473                 Status = STATUS_INVALID_PARAMETER;
474                 break;
475             }
476 
477             /* If we got here, headless is enabled -- we know this much */
478             HeadlessInfo = OutputBuffer;
479             HeadlessInfo->PortType = HeadlessSerialPort;
480             HeadlessInfo->Serial.TerminalAttached = TRUE;
481             HeadlessInfo->Serial.UsedBiosSettings = HeadlessGlobals->UsedBiosSettings != 0;
482             HeadlessInfo->Serial.TerminalBaudRate = HeadlessGlobals->TerminalBaudRate;
483             HeadlessInfo->Serial.TerminalType = HeadlessGlobals->TerminalType;
484 
485             /* Now check on what port/baud it's enabled on */
486             if ((HeadlessGlobals->TerminalPortNumber >= 1) ||
487                 (HeadlessGlobals->UsedBiosSettings))
488             {
489                 /* Get the EMS information */
490                 HeadlessInfo->Serial.TerminalPort = HeadlessGlobals->
491                                                     TerminalPortNumber;
492                 HeadlessInfo->Serial.TerminalPortBaseAddress = HeadlessGlobals->
493                                                                TerminalPortAddress;
494             }
495             else
496             {
497                 /* We don't know for sure */
498                 HeadlessInfo->Serial.TerminalPort = SerialPortUndefined;
499                 HeadlessInfo->Serial.TerminalPortBaseAddress = 0;
500             }
501 
502             /* All done */
503             Status = STATUS_SUCCESS;
504             break;
505         }
506 
507         case HeadlessCmdAddLogEntry:
508             break;
509         case HeadlessCmdDisplayLog:
510             break;
511 
512         case HeadlessCmdSetBlueScreenData:
513         {
514             /* Validate the existence of an input buffer */
515             if (!InputBuffer)
516             {
517                 Status = STATUS_INVALID_PARAMETER;
518                 break;
519             }
520 
521             /* Lie so that we can get Hdl bringup a little bit further */
522             UNIMPLEMENTED;
523             Status = STATUS_SUCCESS;
524             break;
525         }
526 
527         case HeadlessCmdSendBlueScreenData:
528             // TODO: Send XML description of bugcheck.
529             // InputBuffer points to the BugCheckCode.
530             break;
531 
532         case HeadlessCmdQueryGUID:
533             break;
534 
535         case HeadlessCmdPutData:
536         {
537             /* Validate the existence of an input buffer */
538             if (!(InputBuffer) || !(InputBufferSize))
539             {
540                 Status = STATUS_INVALID_PARAMETER;
541                 break;
542             }
543 
544             /* Terminal should be on */
545             if (HeadlessGlobals->TerminalEnabled)
546             {
547                 /* Print each byte in the string making sure VT100 chars are used */
548                 PutString = InputBuffer;
549                 HdlspPutData(PutString->String, InputBufferSize);
550             }
551 
552             /* Return success either way */
553             Status = STATUS_SUCCESS;
554             break;
555         }
556 
557         default:
558             break;
559     }
560 
561     /* Unset processing state */
562     if ((Command != HeadlessCmdAddLogEntry) &&
563         (Command != HeadlessCmdStartBugCheck) &&
564         (Command != HeadlessCmdSendBlueScreenData) &&
565         (Command != HeadlessCmdDoBugCheckProcessing))
566     {
567         ASSERT(HeadlessGlobals->ProcessingCmd == TRUE);
568         HeadlessGlobals->ProcessingCmd = FALSE;
569     }
570 
571     /* All done */
572     return Status;
573 }
574 
575 /*
576  * @implemented
577  */
578 NTSTATUS
579 NTAPI
HeadlessDispatch(IN HEADLESS_CMD Command,IN PVOID InputBuffer,IN SIZE_T InputBufferSize,OUT PVOID OutputBuffer,OUT PSIZE_T OutputBufferSize)580 HeadlessDispatch(IN HEADLESS_CMD Command,
581                  IN PVOID InputBuffer,
582                  IN SIZE_T InputBufferSize,
583                  OUT PVOID OutputBuffer,
584                  OUT PSIZE_T OutputBufferSize)
585 {
586     /* Check for stubs that will expect something even with headless off */
587     if (!HeadlessGlobals)
588     {
589         /* Don't allow the SAC to connect */
590         if (Command == HeadlessCmdEnableTerminal) return STATUS_UNSUCCESSFUL;
591 
592         /* Send bogus reply */
593         if ((Command == HeadlessCmdQueryInformation) ||
594             (Command == HeadlessCmdGetByte) ||
595             (Command == HeadlessCmdGetLine) ||
596             (Command == HeadlessCmdCheckForReboot) ||
597             (Command == HeadlessCmdTerminalPoll))
598         {
599             if (!(OutputBuffer) || !(OutputBufferSize))
600             {
601                 return STATUS_INVALID_PARAMETER;
602             }
603 
604             RtlZeroMemory(OutputBuffer, *OutputBufferSize);
605         }
606 
607         return STATUS_SUCCESS;
608     }
609 
610     /* Do the real work */
611     return HdlspDispatch(Command,
612                          InputBuffer,
613                          InputBufferSize,
614                          OutputBuffer,
615                          OutputBufferSize);
616 }
617 
618 /* EOF */
619