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 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 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 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 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 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 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 VOID 187 NTAPI 188 INIT_FUNCTION 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 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 case HeadlessCmdStartBugCheck: 454 break; 455 case HeadlessCmdDoBugCheckProcessing: 456 break; 457 458 case HeadlessCmdQueryInformation: 459 { 460 /* Make sure the caller passed valid data */ 461 if (!(OutputBuffer) || 462 !(OutputBufferSize) || 463 (*OutputBufferSize < sizeof(*HeadlessInfo))) 464 { 465 DPRINT1("Invalid buffer\n"); 466 Status = STATUS_INVALID_PARAMETER; 467 break; 468 } 469 470 /* If we got here, headless is enabled -- we know this much */ 471 HeadlessInfo = OutputBuffer; 472 HeadlessInfo->PortType = HeadlessSerialPort; 473 HeadlessInfo->Serial.TerminalAttached = TRUE; 474 HeadlessInfo->Serial.UsedBiosSettings = HeadlessGlobals->UsedBiosSettings != 0; 475 HeadlessInfo->Serial.TerminalBaudRate = HeadlessGlobals->TerminalBaudRate; 476 HeadlessInfo->Serial.TerminalType = HeadlessGlobals->TerminalType; 477 478 /* Now check on what port/baud it's enabled on */ 479 if ((HeadlessGlobals->TerminalPortNumber >= 1) || 480 (HeadlessGlobals->UsedBiosSettings)) 481 { 482 /* Get the EMS information */ 483 HeadlessInfo->Serial.TerminalPort = HeadlessGlobals-> 484 TerminalPortNumber; 485 HeadlessInfo->Serial.TerminalPortBaseAddress = HeadlessGlobals-> 486 TerminalPortAddress; 487 } 488 else 489 { 490 /* We don't know for sure */ 491 HeadlessInfo->Serial.TerminalPort = SerialPortUndefined; 492 HeadlessInfo->Serial.TerminalPortBaseAddress = 0; 493 } 494 495 /* All done */ 496 Status = STATUS_SUCCESS; 497 break; 498 } 499 500 case HeadlessCmdAddLogEntry: 501 break; 502 case HeadlessCmdDisplayLog: 503 break; 504 505 case HeadlessCmdSetBlueScreenData: 506 { 507 /* Validate the existence of an input buffer */ 508 if (!InputBuffer) 509 { 510 Status = STATUS_INVALID_PARAMETER; 511 break; 512 } 513 514 /* Lie so that we can get Hdl bringup a little bit further */ 515 UNIMPLEMENTED; 516 Status = STATUS_SUCCESS; 517 break; 518 } 519 520 case HeadlessCmdSendBlueScreenData: 521 break; 522 case HeadlessCmdQueryGUID: 523 break; 524 525 case HeadlessCmdPutData: 526 { 527 /* Validate the existence of an input buffer */ 528 if (!(InputBuffer) || !(InputBufferSize)) 529 { 530 Status = STATUS_INVALID_PARAMETER; 531 break; 532 } 533 534 /* Terminal should be on */ 535 if (HeadlessGlobals->TerminalEnabled) 536 { 537 /* Print each byte in the string making sure VT100 chars are used */ 538 PutString = InputBuffer; 539 HdlspPutData(PutString->String, InputBufferSize); 540 } 541 542 /* Return success either way */ 543 Status = STATUS_SUCCESS; 544 break; 545 } 546 547 default: 548 break; 549 } 550 551 /* Unset processing state */ 552 if ((Command != HeadlessCmdAddLogEntry) && 553 (Command != HeadlessCmdStartBugCheck) && 554 (Command != HeadlessCmdSendBlueScreenData) && 555 (Command != HeadlessCmdDoBugCheckProcessing)) 556 { 557 ASSERT(HeadlessGlobals->ProcessingCmd == TRUE); 558 HeadlessGlobals->ProcessingCmd = FALSE; 559 } 560 561 /* All done */ 562 return Status; 563 } 564 565 /* 566 * @implemented 567 */ 568 NTSTATUS 569 NTAPI 570 HeadlessDispatch(IN HEADLESS_CMD Command, 571 IN PVOID InputBuffer, 572 IN SIZE_T InputBufferSize, 573 OUT PVOID OutputBuffer, 574 OUT PSIZE_T OutputBufferSize) 575 { 576 /* Check for stubs that will expect something even with headless off */ 577 if (!HeadlessGlobals) 578 { 579 /* Don't allow the SAC to connect */ 580 if (Command == HeadlessCmdEnableTerminal) return STATUS_UNSUCCESSFUL; 581 582 /* Send bogus reply */ 583 if ((Command == HeadlessCmdQueryInformation) || 584 (Command == HeadlessCmdGetByte) || 585 (Command == HeadlessCmdGetLine) || 586 (Command == HeadlessCmdCheckForReboot) || 587 (Command == HeadlessCmdTerminalPoll)) 588 { 589 if (!(OutputBuffer) || !(OutputBufferSize)) 590 { 591 return STATUS_INVALID_PARAMETER; 592 } 593 594 RtlZeroMemory(OutputBuffer, *OutputBufferSize); 595 } 596 597 return STATUS_SUCCESS; 598 } 599 600 /* Do the real work */ 601 return HdlspDispatch(Command, 602 InputBuffer, 603 InputBufferSize, 604 OutputBuffer, 605 OutputBufferSize); 606 } 607 608 /* EOF */ 609