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