/** @file Produces Simple Text Input Protocol, Simple Text Input Extended Protocol and Simple Text Output Protocol upon Serial IO Protocol. Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "Terminal.h" // // Globals // EFI_DRIVER_BINDING_PROTOCOL gTerminalDriverBinding = { TerminalDriverBindingSupported, TerminalDriverBindingStart, TerminalDriverBindingStop, 0xa, NULL, NULL }; EFI_GUID *mTerminalType[] = { &gEfiPcAnsiGuid, &gEfiVT100Guid, &gEfiVT100PlusGuid, &gEfiVTUTF8Guid, &gEfiTtyTermGuid }; CHAR16 *mSerialConsoleNames[] = { L"PC-ANSI Serial Console", L"VT-100 Serial Console", L"VT-100+ Serial Console", L"VT-UTF8 Serial Console", L"Tty Terminal Serial Console" }; TERMINAL_DEV mTerminalDevTemplate = { TERMINAL_DEV_SIGNATURE, NULL, 0, NULL, NULL, { // SimpleTextInput TerminalConInReset, TerminalConInReadKeyStroke, NULL }, { // SimpleTextOutput TerminalConOutReset, TerminalConOutOutputString, TerminalConOutTestString, TerminalConOutQueryMode, TerminalConOutSetMode, TerminalConOutSetAttribute, TerminalConOutClearScreen, TerminalConOutSetCursorPosition, TerminalConOutEnableCursor, NULL }, { // SimpleTextOutputMode 1, // MaxMode 0, // Mode EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK), // Attribute 0, // CursorColumn 0, // CursorRow TRUE // CursorVisible }, NULL, // TerminalConsoleModeData 0, // SerialInTimeOut NULL, // RawFifo NULL, // UnicodeFiFo NULL, // EfiKeyFiFo NULL, // EfiKeyFiFoForNotify NULL, // ControllerNameTable NULL, // TimerEvent NULL, // TwoSecondTimeOut INPUT_STATE_DEFAULT, RESET_STATE_DEFAULT, { 0, 0, 0 }, 0, FALSE, { // SimpleTextInputEx TerminalConInResetEx, TerminalConInReadKeyStrokeEx, NULL, TerminalConInSetState, TerminalConInRegisterKeyNotify, TerminalConInUnregisterKeyNotify, }, { // NotifyList NULL, NULL, }, NULL // KeyNotifyProcessEvent }; TERMINAL_CONSOLE_MODE_DATA mTerminalConsoleModeData[] = { {80, 25}, {80, 50}, {100, 31}, // // New modes can be added here. // }; /** Convert the GUID representation of terminal type to enum type. @param Guid The GUID representation of terminal type. @return The terminal type in enum type. **/ TERMINAL_TYPE TerminalTypeFromGuid ( IN EFI_GUID *Guid ) { TERMINAL_TYPE Type; for (Type = 0; Type < ARRAY_SIZE (mTerminalType); Type++) { if (CompareGuid (Guid, mTerminalType[Type])) { break; } } return Type; } /** Test to see if this driver supports Controller. @param This Protocol instance pointer. @param Controller Handle of device to test @param RemainingDevicePath Optional parameter use to pick a specific child device to start. @retval EFI_SUCCESS This driver supports this device. @retval EFI_ALREADY_STARTED This driver is already running on this device. @retval other This driver does not support this device. **/ EFI_STATUS EFIAPI TerminalDriverBindingSupported ( IN EFI_DRIVER_BINDING_PROTOCOL *This, IN EFI_HANDLE Controller, IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath ) { EFI_STATUS Status; EFI_DEVICE_PATH_PROTOCOL *ParentDevicePath; EFI_SERIAL_IO_PROTOCOL *SerialIo; VENDOR_DEVICE_PATH *Node; // // If remaining device path is not NULL, then make sure it is a // device path that describes a terminal communications protocol. // if (RemainingDevicePath != NULL) { // // Check if RemainingDevicePath is the End of Device Path Node, // if yes, go on checking other conditions // if (!IsDevicePathEnd (RemainingDevicePath)) { // // If RemainingDevicePath isn't the End of Device Path Node, // check its validation // Node = (VENDOR_DEVICE_PATH *) RemainingDevicePath; if (Node->Header.Type != MESSAGING_DEVICE_PATH || Node->Header.SubType != MSG_VENDOR_DP || DevicePathNodeLength(&Node->Header) != sizeof(VENDOR_DEVICE_PATH)) { return EFI_UNSUPPORTED; } // // only supports PC ANSI, VT100, VT100+, VT-UTF8, and TtyTerm terminal types // if (TerminalTypeFromGuid (&Node->Guid) == ARRAY_SIZE (mTerminalType)) { return EFI_UNSUPPORTED; } } } // // Open the IO Abstraction(s) needed to perform the supported test // The Controller must support the Serial I/O Protocol. // This driver is a bus driver with at most 1 child device, so it is // ok for it to be already started. // Status = gBS->OpenProtocol ( Controller, &gEfiSerialIoProtocolGuid, (VOID **) &SerialIo, This->DriverBindingHandle, Controller, EFI_OPEN_PROTOCOL_BY_DRIVER ); if (Status == EFI_ALREADY_STARTED) { return EFI_SUCCESS; } if (EFI_ERROR(Status)) { return Status; } // // Close the I/O Abstraction(s) used to perform the supported test // gBS->CloseProtocol ( Controller, &gEfiSerialIoProtocolGuid, This->DriverBindingHandle, Controller ); // // Open the EFI Device Path protocol needed to perform the supported test // Status = gBS->OpenProtocol ( Controller, &gEfiDevicePathProtocolGuid, (VOID **) &ParentDevicePath, This->DriverBindingHandle, Controller, EFI_OPEN_PROTOCOL_BY_DRIVER ); if (Status == EFI_ALREADY_STARTED) { return EFI_SUCCESS; } if (EFI_ERROR(Status)) { return Status; } // // Close protocol, don't use device path protocol in the Support() function // gBS->CloseProtocol ( Controller, &gEfiDevicePathProtocolGuid, This->DriverBindingHandle, Controller ); return Status; } /** Free notify functions list. @param ListHead The list head @retval EFI_SUCCESS Free the notify list successfully. @retval EFI_INVALID_PARAMETER ListHead is NULL. **/ EFI_STATUS TerminalFreeNotifyList ( IN OUT LIST_ENTRY *ListHead ) { TERMINAL_CONSOLE_IN_EX_NOTIFY *NotifyNode; if (ListHead == NULL) { return EFI_INVALID_PARAMETER; } while (!IsListEmpty (ListHead)) { NotifyNode = CR ( ListHead->ForwardLink, TERMINAL_CONSOLE_IN_EX_NOTIFY, NotifyEntry, TERMINAL_CONSOLE_IN_EX_NOTIFY_SIGNATURE ); RemoveEntryList (ListHead->ForwardLink); FreePool(NotifyNode); } return EFI_SUCCESS; } /** Initialize all the text modes which the terminal console supports. It returns information for available text modes that the terminal can support. @param[out] TextModeCount The total number of text modes that terminal console supports. @return The buffer to the text modes column and row information. Caller is responsible to free it when it's non-NULL. **/ TERMINAL_CONSOLE_MODE_DATA * InitializeTerminalConsoleTextMode ( OUT INT32 *TextModeCount ) { TERMINAL_CONSOLE_MODE_DATA *TextModeData; ASSERT (TextModeCount != NULL); TextModeData = AllocateCopyPool (sizeof (mTerminalConsoleModeData), mTerminalConsoleModeData); if (TextModeData == NULL) { return NULL; } *TextModeCount = ARRAY_SIZE (mTerminalConsoleModeData); DEBUG_CODE ( INT32 Index; for (Index = 0; Index < *TextModeCount; Index++) { DEBUG ((DEBUG_INFO, "Terminal - Mode %d, Column = %d, Row = %d\n", Index, TextModeData[Index].Columns, TextModeData[Index].Rows)); } ); return TextModeData; } /** Stop the terminal state machine. @param TerminalDevice The terminal device. **/ VOID StopTerminalStateMachine ( TERMINAL_DEV *TerminalDevice ) { EFI_TPL OriginalTpl; OriginalTpl = gBS->RaiseTPL (TPL_NOTIFY); gBS->CloseEvent (TerminalDevice->TimerEvent); gBS->CloseEvent (TerminalDevice->TwoSecondTimeOut); gBS->RestoreTPL (OriginalTpl); } /** Start the terminal state machine. @param TerminalDevice The terminal device. **/ VOID StartTerminalStateMachine ( TERMINAL_DEV *TerminalDevice ) { EFI_STATUS Status; Status = gBS->CreateEvent ( EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_NOTIFY, TerminalConInTimerHandler, TerminalDevice, &TerminalDevice->TimerEvent ); ASSERT_EFI_ERROR(Status); Status = gBS->SetTimer ( TerminalDevice->TimerEvent, TimerPeriodic, KEYBOARD_TIMER_INTERVAL ); ASSERT_EFI_ERROR(Status); Status = gBS->CreateEvent ( EVT_TIMER, TPL_CALLBACK, NULL, NULL, &TerminalDevice->TwoSecondTimeOut ); ASSERT_EFI_ERROR(Status); } /** Initialize the controller name table. @param TerminalType The terminal type. @param ControllerNameTable The controller name table. @retval EFI_SUCCESS The controller name table is initialized successfully. @retval others Return status of AddUnicodeString2 (). **/ EFI_STATUS InitializeControllerNameTable ( TERMINAL_TYPE TerminalType, EFI_UNICODE_STRING_TABLE **ControllerNameTable ) { EFI_STATUS Status; EFI_UNICODE_STRING_TABLE *Table; ASSERT (TerminalType < ARRAY_SIZE (mTerminalType)); Table = NULL; Status = AddUnicodeString2 ( "eng", gTerminalComponentName.SupportedLanguages, &Table, mSerialConsoleNames[TerminalType], TRUE ); if (!EFI_ERROR(Status)) { Status = AddUnicodeString2 ( "en", gTerminalComponentName2.SupportedLanguages, &Table, mSerialConsoleNames[TerminalType], FALSE ); if (EFI_ERROR(Status)) { FreeUnicodeStringTable (Table); } } if (!EFI_ERROR(Status)) { *ControllerNameTable = Table; } return Status; } /** Start this driver on Controller by opening a Serial IO protocol, reading Device Path, and creating a child handle with a Simple Text In, Simple Text In Ex and Simple Text Out protocol, and device path protocol. And store Console Device Environment Variables. @param This Protocol instance pointer. @param Controller Handle of device to bind driver to @param RemainingDevicePath Optional parameter use to pick a specific child device to start. @retval EFI_SUCCESS This driver is added to Controller. @retval EFI_ALREADY_STARTED This driver is already running on Controller. @retval other This driver does not support this device. **/ EFI_STATUS EFIAPI TerminalDriverBindingStart ( IN EFI_DRIVER_BINDING_PROTOCOL *This, IN EFI_HANDLE Controller, IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath ) { EFI_STATUS Status; EFI_SERIAL_IO_PROTOCOL *SerialIo; EFI_DEVICE_PATH_PROTOCOL *ParentDevicePath; EFI_DEVICE_PATH_PROTOCOL *Vendor; EFI_HANDLE SerialIoHandle; EFI_SERIAL_IO_MODE *Mode; UINTN SerialInTimeOut; TERMINAL_DEV *TerminalDevice; UINT8 TerminalType; EFI_OPEN_PROTOCOL_INFORMATION_ENTRY *OpenInfoBuffer; UINTN EntryCount; UINTN Index; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *SimpleTextOutput; EFI_SIMPLE_TEXT_INPUT_PROTOCOL *SimpleTextInput; EFI_UNICODE_STRING_TABLE *ControllerNameTable; // // Get the Device Path Protocol to build the device path of the child device // Status = gBS->OpenProtocol ( Controller, &gEfiDevicePathProtocolGuid, (VOID **) &ParentDevicePath, This->DriverBindingHandle, Controller, EFI_OPEN_PROTOCOL_BY_DRIVER ); ASSERT ((Status == EFI_SUCCESS) || (Status == EFI_ALREADY_STARTED)); if (EFI_ERROR(Status) && (Status != EFI_ALREADY_STARTED)) { return Status; } // // Open the Serial I/O Protocol BY_DRIVER. It might already be started. // Status = gBS->OpenProtocol ( Controller, &gEfiSerialIoProtocolGuid, (VOID **) &SerialIo, This->DriverBindingHandle, Controller, EFI_OPEN_PROTOCOL_BY_DRIVER ); ASSERT ((Status == EFI_SUCCESS) || (Status == EFI_ALREADY_STARTED)); if (EFI_ERROR(Status) && (Status != EFI_ALREADY_STARTED)) { return Status; } if (!IsHotPlugDevice (ParentDevicePath)) { // // if the serial device is a hot plug device, do not update the // ConInDev, ConOutDev, and StdErrDev variables. // TerminalUpdateConsoleDevVariable (EFI_CON_IN_DEV_VARIABLE_NAME, ParentDevicePath); TerminalUpdateConsoleDevVariable (EFI_CON_OUT_DEV_VARIABLE_NAME, ParentDevicePath); TerminalUpdateConsoleDevVariable (EFI_ERR_OUT_DEV_VARIABLE_NAME, ParentDevicePath); } // // Do not create any child for END remaining device path. // if ((RemainingDevicePath != NULL) && IsDevicePathEnd (RemainingDevicePath)) { return EFI_SUCCESS; } if (Status == EFI_ALREADY_STARTED) { if (RemainingDevicePath == NULL) { // // If RemainingDevicePath is NULL or is the End of Device Path Node // return EFI_SUCCESS; } // // This driver can only produce one child per serial port. // Change its terminal type as remaining device path requests. // Status = gBS->OpenProtocolInformation ( Controller, &gEfiSerialIoProtocolGuid, &OpenInfoBuffer, &EntryCount ); if (!EFI_ERROR(Status)) { Status = EFI_NOT_FOUND; for (Index = 0; Index < EntryCount; Index++) { if ((OpenInfoBuffer[Index].Attributes & EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER) != 0) { Status = gBS->OpenProtocol ( OpenInfoBuffer[Index].ControllerHandle, &gEfiSimpleTextInProtocolGuid, (VOID **) &SimpleTextInput, This->DriverBindingHandle, Controller, EFI_OPEN_PROTOCOL_GET_PROTOCOL ); if (!EFI_ERROR(Status)) { TerminalDevice = TERMINAL_CON_IN_DEV_FROM_THIS (SimpleTextInput); TerminalType = TerminalTypeFromGuid (&((VENDOR_DEVICE_PATH *) RemainingDevicePath)->Guid); ASSERT (TerminalType < ARRAY_SIZE (mTerminalType)); if (TerminalDevice->TerminalType != TerminalType) { Status = InitializeControllerNameTable (TerminalType, &ControllerNameTable); if (!EFI_ERROR(Status)) { StopTerminalStateMachine (TerminalDevice); // // Update the device path // Vendor = TerminalDevice->DevicePath; Status = gBS->LocateDevicePath (&gEfiSerialIoProtocolGuid, &Vendor, &SerialIoHandle); ASSERT_EFI_ERROR(Status); CopyGuid (&((VENDOR_DEVICE_PATH *) Vendor)->Guid, mTerminalType[TerminalType]); Status = gBS->ReinstallProtocolInterface ( TerminalDevice->Handle, &gEfiDevicePathProtocolGuid, TerminalDevice->DevicePath, TerminalDevice->DevicePath ); if (!EFI_ERROR(Status)) { TerminalDevice->TerminalType = TerminalType; StartTerminalStateMachine (TerminalDevice); FreeUnicodeStringTable (TerminalDevice->ControllerNameTable); TerminalDevice->ControllerNameTable = ControllerNameTable; } else { // // Restore the device path on failure // CopyGuid (&((VENDOR_DEVICE_PATH *) Vendor)->Guid, mTerminalType[TerminalDevice->TerminalType]); FreeUnicodeStringTable (ControllerNameTable); } } } } break; } } FreePool(OpenInfoBuffer); } return Status; } // // Initialize the Terminal Dev // TerminalDevice = AllocateCopyPool (sizeof (TERMINAL_DEV), &mTerminalDevTemplate); if (TerminalDevice == NULL) { Status = EFI_OUT_OF_RESOURCES; goto CloseProtocols; } if (RemainingDevicePath == NULL) { // // If RemainingDevicePath is NULL, use default terminal type // TerminalDevice->TerminalType = PcdGet8 (PcdDefaultTerminalType); } else { // // End of Device Path Node is handled in above. // ASSERT (!IsDevicePathEnd (RemainingDevicePath)); // // If RemainingDevicePath isn't the End of Device Path Node, // Use the RemainingDevicePath to determine the terminal type // TerminalDevice->TerminalType = TerminalTypeFromGuid (&((VENDOR_DEVICE_PATH *) RemainingDevicePath)->Guid); } ASSERT (TerminalDevice->TerminalType < ARRAY_SIZE (mTerminalType)); TerminalDevice->SerialIo = SerialIo; // // Build the component name for the child device // Status = InitializeControllerNameTable (TerminalDevice->TerminalType, &TerminalDevice->ControllerNameTable); if (EFI_ERROR(Status)) { goto FreeResources; } // // Build the device path for the child device // Status = SetTerminalDevicePath (TerminalDevice->TerminalType, ParentDevicePath, &TerminalDevice->DevicePath); if (EFI_ERROR(Status)) { goto FreeResources; } InitializeListHead (&TerminalDevice->NotifyList); Status = gBS->CreateEvent ( EVT_NOTIFY_WAIT, TPL_NOTIFY, TerminalConInWaitForKeyEx, TerminalDevice, &TerminalDevice->SimpleInputEx.WaitForKeyEx ); ASSERT_EFI_ERROR(Status); Status = gBS->CreateEvent ( EVT_NOTIFY_WAIT, TPL_NOTIFY, TerminalConInWaitForKey, TerminalDevice, &TerminalDevice->SimpleInput.WaitForKey ); ASSERT_EFI_ERROR(Status); Status = gBS->CreateEvent ( EVT_NOTIFY_SIGNAL, TPL_CALLBACK, KeyNotifyProcessHandler, TerminalDevice, &TerminalDevice->KeyNotifyProcessEvent ); ASSERT_EFI_ERROR(Status); // // Allocates and initializes the FIFO buffer to be zero, used for accommodating // the pre-read pending characters. // TerminalDevice->RawFiFo = AllocateZeroPool (sizeof (RAW_DATA_FIFO)); if (TerminalDevice->RawFiFo == NULL) { goto FreeResources; } TerminalDevice->UnicodeFiFo = AllocateZeroPool (sizeof (UNICODE_FIFO)); if (TerminalDevice->UnicodeFiFo == NULL) { goto FreeResources; } TerminalDevice->EfiKeyFiFo = AllocateZeroPool (sizeof (EFI_KEY_FIFO)); if (TerminalDevice->EfiKeyFiFo == NULL) { goto FreeResources; } TerminalDevice->EfiKeyFiFoForNotify = AllocateZeroPool (sizeof (EFI_KEY_FIFO)); if (TerminalDevice->EfiKeyFiFoForNotify == NULL) { goto FreeResources; } // // Set the timeout value of serial buffer for keystroke response performance issue // Mode = TerminalDevice->SerialIo->Mode; SerialInTimeOut = 0; if (Mode->BaudRate != 0) { SerialInTimeOut = (1 + Mode->DataBits + Mode->StopBits) * 2 * 1000000 / (UINTN) Mode->BaudRate; } Status = TerminalDevice->SerialIo->SetAttributes ( TerminalDevice->SerialIo, Mode->BaudRate, Mode->ReceiveFifoDepth, (UINT32) SerialInTimeOut, (EFI_PARITY_TYPE) (Mode->Parity), (UINT8) Mode->DataBits, (EFI_STOP_BITS_TYPE) (Mode->StopBits) ); if (EFI_ERROR(Status)) { // // if set attributes operation fails, invalidate // the value of SerialInTimeOut,thus make it // inconsistent with the default timeout value // of serial buffer. This will invoke the recalculation // in the readkeystroke routine. // TerminalDevice->SerialInTimeOut = 0; } else { TerminalDevice->SerialInTimeOut = SerialInTimeOut; } SimpleTextOutput = &TerminalDevice->SimpleTextOutput; SimpleTextInput = &TerminalDevice->SimpleInput; // // Initialize SimpleTextOut instance // SimpleTextOutput->Mode = &TerminalDevice->SimpleTextOutputMode; TerminalDevice->TerminalConsoleModeData = InitializeTerminalConsoleTextMode ( &SimpleTextOutput->Mode->MaxMode ); if (TerminalDevice->TerminalConsoleModeData == NULL) { goto FreeResources; } // // For terminal devices, cursor is always visible // SimpleTextOutput->Mode->CursorVisible = TRUE; Status = SimpleTextOutput->SetAttribute (SimpleTextOutput, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK)); if (!EFI_ERROR(Status)) { Status = SimpleTextOutput->Reset (SimpleTextOutput, FALSE); } if (EFI_ERROR(Status)) { goto ReportError; } // // Initialize SimpleTextInput instance // Status = SimpleTextInput->Reset (SimpleTextInput, FALSE); if (EFI_ERROR(Status)) { goto ReportError; } Status = gBS->InstallMultipleProtocolInterfaces ( &TerminalDevice->Handle, &gEfiSimpleTextInProtocolGuid, &TerminalDevice->SimpleInput, &gEfiSimpleTextInputExProtocolGuid, &TerminalDevice->SimpleInputEx, &gEfiSimpleTextOutProtocolGuid, &TerminalDevice->SimpleTextOutput, &gEfiDevicePathProtocolGuid, TerminalDevice->DevicePath, NULL ); if (!EFI_ERROR(Status)) { Status = gBS->OpenProtocol ( Controller, &gEfiSerialIoProtocolGuid, (VOID **) &TerminalDevice->SerialIo, This->DriverBindingHandle, TerminalDevice->Handle, EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER ); ASSERT_EFI_ERROR(Status); StartTerminalStateMachine (TerminalDevice); return Status; } ReportError: REPORT_STATUS_CODE_WITH_DEVICE_PATH ( EFI_ERROR_CODE | EFI_ERROR_MINOR, (EFI_PERIPHERAL_REMOTE_CONSOLE | EFI_P_EC_CONTROLLER_ERROR), ParentDevicePath ); FreeResources: ASSERT (TerminalDevice != NULL); if (TerminalDevice->SimpleInput.WaitForKey != NULL) { gBS->CloseEvent (TerminalDevice->SimpleInput.WaitForKey); } if (TerminalDevice->SimpleInputEx.WaitForKeyEx != NULL) { gBS->CloseEvent (TerminalDevice->SimpleInputEx.WaitForKeyEx); } if (TerminalDevice->KeyNotifyProcessEvent != NULL) { gBS->CloseEvent (TerminalDevice->KeyNotifyProcessEvent); } if (TerminalDevice->RawFiFo != NULL) { FreePool(TerminalDevice->RawFiFo); } if (TerminalDevice->UnicodeFiFo != NULL) { FreePool(TerminalDevice->UnicodeFiFo); } if (TerminalDevice->EfiKeyFiFo != NULL) { FreePool(TerminalDevice->EfiKeyFiFo); } if (TerminalDevice->EfiKeyFiFoForNotify != NULL) { FreePool(TerminalDevice->EfiKeyFiFoForNotify); } if (TerminalDevice->ControllerNameTable != NULL) { FreeUnicodeStringTable (TerminalDevice->ControllerNameTable); } if (TerminalDevice->DevicePath != NULL) { FreePool(TerminalDevice->DevicePath); } if (TerminalDevice->TerminalConsoleModeData != NULL) { FreePool(TerminalDevice->TerminalConsoleModeData); } FreePool(TerminalDevice); CloseProtocols: // // Remove Parent Device Path from // the Console Device Environment Variables // TerminalRemoveConsoleDevVariable (EFI_CON_IN_DEV_VARIABLE_NAME, ParentDevicePath); TerminalRemoveConsoleDevVariable (EFI_CON_OUT_DEV_VARIABLE_NAME, ParentDevicePath); TerminalRemoveConsoleDevVariable (EFI_ERR_OUT_DEV_VARIABLE_NAME, ParentDevicePath); Status = gBS->CloseProtocol ( Controller, &gEfiSerialIoProtocolGuid, This->DriverBindingHandle, Controller ); ASSERT_EFI_ERROR(Status); Status = gBS->CloseProtocol ( Controller, &gEfiDevicePathProtocolGuid, This->DriverBindingHandle, Controller ); ASSERT_EFI_ERROR(Status); return Status; } /** Stop this driver on Controller by closing Simple Text In, Simple Text In Ex, Simple Text Out protocol, and removing parent device path from Console Device Environment Variables. @param This Protocol instance pointer. @param Controller Handle of device to stop driver on @param NumberOfChildren Number of Handles in ChildHandleBuffer. If number of children is zero stop the entire bus driver. @param ChildHandleBuffer List of Child Handles to Stop. @retval EFI_SUCCESS This driver is removed Controller. @retval other This driver could not be removed from this device. **/ EFI_STATUS EFIAPI TerminalDriverBindingStop ( IN EFI_DRIVER_BINDING_PROTOCOL *This, IN EFI_HANDLE Controller, IN UINTN NumberOfChildren, IN EFI_HANDLE *ChildHandleBuffer ) { EFI_STATUS Status; UINTN Index; BOOLEAN AllChildrenStopped; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *SimpleTextOutput; TERMINAL_DEV *TerminalDevice; EFI_DEVICE_PATH_PROTOCOL *ParentDevicePath; EFI_SERIAL_IO_PROTOCOL *SerialIo; // // Complete all outstanding transactions to Controller. // Don't allow any new transaction to Controller to be started. // if (NumberOfChildren == 0) { // // Close the bus driver // Status = gBS->OpenProtocol ( Controller, &gEfiDevicePathProtocolGuid, (VOID **) &ParentDevicePath, This->DriverBindingHandle, Controller, EFI_OPEN_PROTOCOL_GET_PROTOCOL ); ASSERT_EFI_ERROR(Status); // // Remove Parent Device Path from // the Console Device Environment Variables // TerminalRemoveConsoleDevVariable (EFI_CON_IN_DEV_VARIABLE_NAME, ParentDevicePath); TerminalRemoveConsoleDevVariable (EFI_CON_OUT_DEV_VARIABLE_NAME, ParentDevicePath); TerminalRemoveConsoleDevVariable (EFI_ERR_OUT_DEV_VARIABLE_NAME, ParentDevicePath); gBS->CloseProtocol ( Controller, &gEfiSerialIoProtocolGuid, This->DriverBindingHandle, Controller ); gBS->CloseProtocol ( Controller, &gEfiDevicePathProtocolGuid, This->DriverBindingHandle, Controller ); return EFI_SUCCESS; } AllChildrenStopped = TRUE; for (Index = 0; Index < NumberOfChildren; Index++) { Status = gBS->OpenProtocol ( ChildHandleBuffer[Index], &gEfiSimpleTextOutProtocolGuid, (VOID **) &SimpleTextOutput, This->DriverBindingHandle, ChildHandleBuffer[Index], EFI_OPEN_PROTOCOL_GET_PROTOCOL ); if (!EFI_ERROR(Status)) { TerminalDevice = TERMINAL_CON_OUT_DEV_FROM_THIS (SimpleTextOutput); gBS->CloseProtocol ( Controller, &gEfiSerialIoProtocolGuid, This->DriverBindingHandle, ChildHandleBuffer[Index] ); Status = gBS->UninstallMultipleProtocolInterfaces ( ChildHandleBuffer[Index], &gEfiSimpleTextInProtocolGuid, &TerminalDevice->SimpleInput, &gEfiSimpleTextInputExProtocolGuid, &TerminalDevice->SimpleInputEx, &gEfiSimpleTextOutProtocolGuid, &TerminalDevice->SimpleTextOutput, &gEfiDevicePathProtocolGuid, TerminalDevice->DevicePath, NULL ); if (EFI_ERROR(Status)) { gBS->OpenProtocol ( Controller, &gEfiSerialIoProtocolGuid, (VOID **) &SerialIo, This->DriverBindingHandle, ChildHandleBuffer[Index], EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER ); } else { FreeUnicodeStringTable (TerminalDevice->ControllerNameTable); StopTerminalStateMachine (TerminalDevice); gBS->CloseEvent (TerminalDevice->SimpleInput.WaitForKey); gBS->CloseEvent (TerminalDevice->SimpleInputEx.WaitForKeyEx); gBS->CloseEvent (TerminalDevice->KeyNotifyProcessEvent); TerminalFreeNotifyList (&TerminalDevice->NotifyList); FreePool(TerminalDevice->DevicePath); FreePool(TerminalDevice->TerminalConsoleModeData); FreePool(TerminalDevice); } } if (EFI_ERROR(Status)) { AllChildrenStopped = FALSE; } } if (!AllChildrenStopped) { return EFI_DEVICE_ERROR; } return EFI_SUCCESS; } /** Compare a device path data structure to that of all the nodes of a second device path instance. @param Multi A pointer to a multi-instance device path data structure. @param Single A pointer to a single-instance device path data structure. @retval TRUE If the Single is contained within Multi. @retval FALSE The Single is not match within Multi. **/ BOOLEAN MatchDevicePaths ( IN EFI_DEVICE_PATH_PROTOCOL *Multi, IN EFI_DEVICE_PATH_PROTOCOL *Single ) { EFI_DEVICE_PATH_PROTOCOL *DevicePath; EFI_DEVICE_PATH_PROTOCOL *DevicePathInst; UINTN Size; DevicePath = Multi; DevicePathInst = GetNextDevicePathInstance (&DevicePath, &Size); // // Search for the match of 'Single' in 'Multi' // while (DevicePathInst != NULL) { // // If the single device path is found in multiple device paths, // return success // if (CompareMem (Single, DevicePathInst, Size) == 0) { FreePool(DevicePathInst); return TRUE; } FreePool(DevicePathInst); DevicePathInst = GetNextDevicePathInstance (&DevicePath, &Size); } return FALSE; } /** Update terminal device path in Console Device Environment Variables. @param VariableName The Console Device Environment Variable. @param ParentDevicePath The terminal device path to be updated. **/ VOID TerminalUpdateConsoleDevVariable ( IN CHAR16 *VariableName, IN EFI_DEVICE_PATH_PROTOCOL *ParentDevicePath ) { EFI_STATUS Status; UINTN NameSize; UINTN VariableSize; TERMINAL_TYPE TerminalType; EFI_DEVICE_PATH_PROTOCOL *Variable; EFI_DEVICE_PATH_PROTOCOL *NewVariable; EFI_DEVICE_PATH_PROTOCOL *TempDevicePath; EDKII_SET_VARIABLE_STATUS *SetVariableStatus; // // Get global variable and its size according to the name given. // Status = GetEfiGlobalVariable2 (VariableName, (VOID**)&Variable, NULL); if (Status == EFI_NOT_FOUND) { Status = EFI_SUCCESS; Variable = NULL; } if (EFI_ERROR(Status)) { return; } // // Append terminal device path onto the variable. // for (TerminalType = 0; TerminalType < ARRAY_SIZE (mTerminalType); TerminalType++) { SetTerminalDevicePath (TerminalType, ParentDevicePath, &TempDevicePath); if (TempDevicePath != NULL) { if (!MatchDevicePaths (Variable, TempDevicePath)) { NewVariable = AppendDevicePathInstance (Variable, TempDevicePath); if (NewVariable != NULL) { if (Variable != NULL) { FreePool(Variable); } Variable = NewVariable; } } FreePool(TempDevicePath); } } VariableSize = GetDevicePathSize (Variable); Status = gRT->SetVariable ( VariableName, &gEfiGlobalVariableGuid, EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, VariableSize, Variable ); if (EFI_ERROR(Status)) { NameSize = StrSize (VariableName); SetVariableStatus = AllocatePool (sizeof (EDKII_SET_VARIABLE_STATUS) + NameSize + VariableSize); if (SetVariableStatus != NULL) { CopyGuid (&SetVariableStatus->Guid, &gEfiGlobalVariableGuid); SetVariableStatus->NameSize = NameSize; SetVariableStatus->DataSize = VariableSize; SetVariableStatus->SetStatus = Status; SetVariableStatus->Attributes = EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS; CopyMem (SetVariableStatus + 1, VariableName, NameSize); CopyMem (((UINT8 *) (SetVariableStatus + 1)) + NameSize, Variable, VariableSize); REPORT_STATUS_CODE_EX ( EFI_ERROR_CODE, PcdGet32 (PcdErrorCodeSetVariable), 0, NULL, &gEdkiiStatusCodeDataTypeVariableGuid, SetVariableStatus, sizeof (EDKII_SET_VARIABLE_STATUS) + NameSize + VariableSize ); FreePool(SetVariableStatus); } } FreePool(Variable); return ; } /** Remove terminal device path from Console Device Environment Variables. @param VariableName Console Device Environment Variables. @param ParentDevicePath The terminal device path to be updated. **/ VOID TerminalRemoveConsoleDevVariable ( IN CHAR16 *VariableName, IN EFI_DEVICE_PATH_PROTOCOL *ParentDevicePath ) { EFI_STATUS Status; BOOLEAN FoundOne; BOOLEAN Match; UINTN VariableSize; UINTN InstanceSize; TERMINAL_TYPE TerminalType; EFI_DEVICE_PATH_PROTOCOL *Instance; EFI_DEVICE_PATH_PROTOCOL *Variable; EFI_DEVICE_PATH_PROTOCOL *OriginalVariable; EFI_DEVICE_PATH_PROTOCOL *NewVariable; EFI_DEVICE_PATH_PROTOCOL *SavedNewVariable; EFI_DEVICE_PATH_PROTOCOL *TempDevicePath; Instance = NULL; // // Get global variable and its size according to the name given. // GetEfiGlobalVariable2 (VariableName, (VOID**)&Variable, NULL); if (Variable == NULL) { return ; } FoundOne = FALSE; OriginalVariable = Variable; NewVariable = NULL; // // Get first device path instance from Variable // Instance = GetNextDevicePathInstance (&Variable, &InstanceSize); if (Instance == NULL) { FreePool(OriginalVariable); return ; } // // Loop through all the device path instances of Variable // do { // // Loop through all the terminal types that this driver supports // Match = FALSE; for (TerminalType = 0; TerminalType < ARRAY_SIZE (mTerminalType); TerminalType++) { SetTerminalDevicePath (TerminalType, ParentDevicePath, &TempDevicePath); // // Compare the generated device path to the current device path instance // if (TempDevicePath != NULL) { if (CompareMem (Instance, TempDevicePath, InstanceSize) == 0) { Match = TRUE; FoundOne = TRUE; } FreePool(TempDevicePath); } } // // If a match was not found, then keep the current device path instance // if (!Match) { SavedNewVariable = NewVariable; NewVariable = AppendDevicePathInstance (NewVariable, Instance); if (SavedNewVariable != NULL) { FreePool(SavedNewVariable); } } // // Get next device path instance from Variable // FreePool(Instance); Instance = GetNextDevicePathInstance (&Variable, &InstanceSize); } while (Instance != NULL); FreePool(OriginalVariable); if (FoundOne) { VariableSize = GetDevicePathSize (NewVariable); Status = gRT->SetVariable ( VariableName, &gEfiGlobalVariableGuid, EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, VariableSize, NewVariable ); // // Shrinking variable with existing variable driver implementation shouldn't fail. // ASSERT_EFI_ERROR(Status); } if (NewVariable != NULL) { FreePool(NewVariable); } return ; } /** Build terminal device path according to terminal type. @param TerminalType The terminal type is PC ANSI, VT100, VT100+ or VT-UTF8. @param ParentDevicePath Parent device path. @param TerminalDevicePath Returned terminal device path, if building successfully. @retval EFI_UNSUPPORTED Terminal does not belong to the supported type. @retval EFI_OUT_OF_RESOURCES Generate terminal device path failed. @retval EFI_SUCCESS Build terminal device path successfully. **/ EFI_STATUS SetTerminalDevicePath ( IN TERMINAL_TYPE TerminalType, IN EFI_DEVICE_PATH_PROTOCOL *ParentDevicePath, OUT EFI_DEVICE_PATH_PROTOCOL **TerminalDevicePath ) { VENDOR_DEVICE_PATH Node; ASSERT (TerminalType < ARRAY_SIZE (mTerminalType)); Node.Header.Type = MESSAGING_DEVICE_PATH; Node.Header.SubType = MSG_VENDOR_DP; SetDevicePathNodeLength (&Node.Header, sizeof (VENDOR_DEVICE_PATH)); CopyGuid (&Node.Guid, mTerminalType[TerminalType]); // // Append the terminal node onto parent device path // to generate a complete terminal device path. // *TerminalDevicePath = AppendDevicePathNode ( ParentDevicePath, (EFI_DEVICE_PATH_PROTOCOL *) &Node ); if (*TerminalDevicePath == NULL) { return EFI_OUT_OF_RESOURCES; } return EFI_SUCCESS; } /** The user Entry Point for module Terminal. The user code starts with this function. @param ImageHandle The firmware allocated handle for the EFI image. @param SystemTable A pointer to the EFI System Table. @retval EFI_SUCCESS The entry point is executed successfully. @retval other Some error occurs when executing this entry point. **/ EFI_STATUS EFIAPI InitializeTerminal( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; // // Install driver model protocol(s). // Status = EfiLibInstallDriverBindingComponentName2 ( ImageHandle, SystemTable, &gTerminalDriverBinding, ImageHandle, &gTerminalComponentName, &gTerminalComponentName2 ); ASSERT_EFI_ERROR(Status); return Status; } /** Check if the device supports hot-plug through its device path. This function could be updated to check more types of Hot Plug devices. Currently, it checks USB and PCCard device. @param DevicePath Pointer to device's device path. @retval TRUE The devcie is a hot-plug device @retval FALSE The devcie is not a hot-plug device. **/ BOOLEAN IsHotPlugDevice ( IN EFI_DEVICE_PATH_PROTOCOL *DevicePath ) { EFI_DEVICE_PATH_PROTOCOL *CheckDevicePath; CheckDevicePath = DevicePath; while (!IsDevicePathEnd (CheckDevicePath)) { // // Check device whether is hot plug device or not throught Device Path // if ((DevicePathType (CheckDevicePath) == MESSAGING_DEVICE_PATH) && (DevicePathSubType (CheckDevicePath) == MSG_USB_DP || DevicePathSubType (CheckDevicePath) == MSG_USB_CLASS_DP || DevicePathSubType (CheckDevicePath) == MSG_USB_WWID_DP)) { // // If Device is USB device // return TRUE; } if ((DevicePathType (CheckDevicePath) == HARDWARE_DEVICE_PATH) && (DevicePathSubType (CheckDevicePath) == HW_PCCARD_DP)) { // // If Device is PCCard // return TRUE; } CheckDevicePath = NextDevicePathNode (CheckDevicePath); } return FALSE; }