/** @file Driver for virtio-serial devices. The virtio serial device also known as virtio console device because initially it had only support for a single tty, intended to be used as console. Support for multiple streams and named data ports has been added later on. https://docs.oasis-open.org/virtio/virtio/v1.2/cs01/virtio-v1.2-cs01.html#x1-2900003 SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include #include "VirtioSerial.h" STATIC LIST_ENTRY mVirtioSerialList; STATIC CONST CHAR8 *EventNames[] = { [VIRTIO_SERIAL_DEVICE_READY] = "device-ready", [VIRTIO_SERIAL_DEVICE_ADD] = "device-add", [VIRTIO_SERIAL_DEVICE_REMOVE] = "device-remove", [VIRTIO_SERIAL_PORT_READY] = "port-ready", [VIRTIO_SERIAL_CONSOLE_PORT] = "console-port", [VIRTIO_SERIAL_RESIZE] = "resize", [VIRTIO_SERIAL_PORT_OPEN] = "port-open", [VIRTIO_SERIAL_PORT_NAME] = "port-name", }; VOID EFIAPI LogDevicePath ( UINT32 Level, const CHAR8 *Func, CHAR16 *Note, EFI_DEVICE_PATH_PROTOCOL *DevicePath ) { CHAR16 *Str; Str = ConvertDevicePathToText (DevicePath, FALSE, FALSE); if (!Str) { DEBUG ((DEBUG_INFO, "ConvertDevicePathToText failed\n")); return; } DEBUG ((Level, "%a: %s%s%s\n", Func, Note ? Note : L"", Note ? L": " : L"", Str)); FreePool (Str); } EFI_STATUS EFIAPI VirtioSerialTxControl ( IN OUT VIRTIO_SERIAL_DEV *Dev, IN UINT32 Id, IN UINT16 Event, IN UINT16 Value ) { VIRTIO_SERIAL_CONTROL Control; Control.Id = Id; Control.Event = Event; Control.Value = Value; DEBUG (( DEBUG_INFO, "%a:%d: >>> event %a, port-id %d, value %d\n", __func__, __LINE__, EventNames[Control.Event], Control.Id, Control.Value )); VirtioSerialRingClearTx (Dev, VIRTIO_SERIAL_Q_TX_CTRL); return VirtioSerialRingSendBuffer (Dev, VIRTIO_SERIAL_Q_TX_CTRL, &Control, sizeof (Control), TRUE); } STATIC VOID EFIAPI VirtioSerialRxControl ( IN OUT VIRTIO_SERIAL_DEV *Dev ) { UINT8 Data[CTRL_RX_BUFSIZE+1]; UINT32 DataSize; VIRTIO_SERIAL_CONTROL Control; EFI_STATUS Status; BOOLEAN HasData; UINT16 Ready; for ( ; ;) { HasData = VirtioSerialRingGetBuffer (Dev, VIRTIO_SERIAL_Q_RX_CTRL, Data, &DataSize); if (!HasData) { return; } if (DataSize < sizeof (Control)) { DEBUG (( DEBUG_ERROR, "%a:%d: length mismatch: %d != %d\n", __func__, __LINE__, DataSize, sizeof (Control) )); continue; } CopyMem (&Control, Data, sizeof (Control)); if (Control.Event < ARRAY_SIZE (EventNames)) { DEBUG (( DEBUG_INFO, "%a:%d: <<< event %a, port-id %d, value %d\n", __func__, __LINE__, EventNames[Control.Event], Control.Id, Control.Value )); } else { DEBUG (( DEBUG_ERROR, "%a:%d: unknown event: %d\n", __func__, __LINE__, Control.Event )); } switch (Control.Event) { case VIRTIO_SERIAL_DEVICE_ADD: if (Control.Id < MAX_PORTS) { Status = VirtioSerialPortAdd (Dev, Control.Id); Ready = (Status == EFI_SUCCESS) ? 1 : 0; } else { Ready = 0; } VirtioSerialTxControl (Dev, Control.Id, VIRTIO_SERIAL_PORT_READY, Ready); if (Ready) { Dev->NumPorts++; } break; case VIRTIO_SERIAL_DEVICE_REMOVE: if (Control.Id < MAX_PORTS) { VirtioSerialPortRemove (Dev, Control.Id); } break; case VIRTIO_SERIAL_CONSOLE_PORT: if (Control.Id < MAX_PORTS) { VirtioSerialPortSetConsole (Dev, Control.Id); Dev->NumConsoles++; } break; case VIRTIO_SERIAL_PORT_NAME: if (Control.Id < MAX_PORTS) { Data[DataSize] = 0; VirtioSerialPortSetName (Dev, Control.Id, Data + sizeof (Control)); Dev->NumNamedPorts++; } break; case VIRTIO_SERIAL_PORT_OPEN: if (Control.Id < MAX_PORTS) { VirtioSerialPortSetDeviceOpen (Dev, Control.Id, Control.Value); } break; default: break; } } } STATIC VOID EFIAPI VirtioSerialTimer ( IN EFI_EVENT Event, IN VOID *Context ) { VIRTIO_SERIAL_DEV *Dev = Context; VirtioSerialRxControl (Dev); } STATIC VOID EFIAPI VirtioSerialUninitAllRings ( IN OUT VIRTIO_SERIAL_DEV *Dev ) { UINT16 Index; for (Index = 0; Index < MAX_RINGS; Index++) { VirtioSerialUninitRing (Dev, Index); } } STATIC EFI_STATUS EFIAPI VirtioSerialInit ( IN OUT VIRTIO_SERIAL_DEV *Dev ) { UINT8 NextDevStat; EFI_STATUS Status; UINT64 Features; UINTN Retries; // // Execute virtio-0.9.5, 2.2.1 Device Initialization Sequence. // NextDevStat = 0; // step 1 -- reset device Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat); if (EFI_ERROR (Status)) { goto Failed; } NextDevStat |= VSTAT_ACK; // step 2 -- acknowledge device presence Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat); if (EFI_ERROR (Status)) { goto Failed; } NextDevStat |= VSTAT_DRIVER; // step 3 -- we know how to drive it Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat); if (EFI_ERROR (Status)) { goto Failed; } // // Set Page Size - MMIO VirtIo Specific // Status = Dev->VirtIo->SetPageSize (Dev->VirtIo, EFI_PAGE_SIZE); if (EFI_ERROR (Status)) { goto Failed; } // // step 4a -- retrieve and validate features // Status = Dev->VirtIo->GetDeviceFeatures (Dev->VirtIo, &Features); if (EFI_ERROR (Status)) { goto Failed; } Features &= (VIRTIO_F_VERSION_1 | VIRTIO_F_IOMMU_PLATFORM | VIRTIO_SERIAL_F_MULTIPORT); // // In virtio-1.0, feature negotiation is expected to complete before queue // discovery, and the device can also reject the selected set of features. // if (Dev->VirtIo->Revision >= VIRTIO_SPEC_REVISION (1, 0, 0)) { Status = Virtio10WriteFeatures (Dev->VirtIo, Features, &NextDevStat); if (EFI_ERROR (Status)) { goto Failed; } } DEBUG (( DEBUG_INFO, "%a:%d: features ok:%a%a%a\n", __func__, __LINE__, (Features & VIRTIO_F_VERSION_1) ? " v1.0" : "", (Features & VIRTIO_F_IOMMU_PLATFORM) ? " iommu" : "", (Features & VIRTIO_SERIAL_F_MULTIPORT) ? " multiport" : "" )); if (Features & VIRTIO_SERIAL_F_MULTIPORT) { Dev->VirtIo->ReadDevice ( Dev->VirtIo, OFFSET_OF (VIRTIO_SERIAL_CONFIG, MaxPorts), sizeof (Dev->Config.MaxPorts), sizeof (Dev->Config.MaxPorts), &Dev->Config.MaxPorts ); DEBUG (( DEBUG_INFO, "%a:%d: max device ports: %d\n", __func__, __LINE__, Dev->Config.MaxPorts )); } Status = VirtioSerialInitRing (Dev, VIRTIO_SERIAL_Q_RX_CTRL, CTRL_RX_BUFSIZE); if (EFI_ERROR (Status)) { goto Failed; } Status = VirtioSerialInitRing (Dev, VIRTIO_SERIAL_Q_TX_CTRL, CTRL_TX_BUFSIZE); if (EFI_ERROR (Status)) { goto Failed; } // // step 5 -- Report understood features and guest-tuneables. // if (Dev->VirtIo->Revision < VIRTIO_SPEC_REVISION (1, 0, 0)) { Features &= ~(UINT64)(VIRTIO_F_VERSION_1 | VIRTIO_F_IOMMU_PLATFORM); Status = Dev->VirtIo->SetGuestFeatures (Dev->VirtIo, Features); if (EFI_ERROR (Status)) { goto Failed; } } // // step 6 -- initialization complete // NextDevStat |= VSTAT_DRIVER_OK; Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat); if (EFI_ERROR (Status)) { goto Failed; } VirtioSerialRingFillRx (Dev, VIRTIO_SERIAL_Q_RX_CTRL); VirtioSerialTxControl (Dev, 0, VIRTIO_SERIAL_DEVICE_READY, 1); for (Retries = 0; Retries < 100; Retries++) { gBS->Stall (1000); VirtioSerialRxControl (Dev); if (Dev->NumPorts && (Dev->NumConsoles + Dev->NumNamedPorts == Dev->NumPorts)) { // port discovery complete break; } } Status = gBS->CreateEvent ( EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_NOTIFY, VirtioSerialTimer, Dev, &Dev->Timer ); if (EFI_ERROR (Status)) { goto Failed; } Status = gBS->SetTimer ( Dev->Timer, TimerPeriodic, EFI_TIMER_PERIOD_MILLISECONDS (10) ); if (EFI_ERROR (Status)) { goto Failed; } DEBUG (( DEBUG_INFO, "%a:%d: OK, %d consoles, %d named ports\n", __func__, __LINE__, Dev->NumConsoles, Dev->NumNamedPorts )); return EFI_SUCCESS; Failed: VirtioSerialUninitAllRings (Dev); // // Notify the host about our failure to setup: virtio-0.9.5, 2.2.2.1 Device // Status. VirtIo access failure here should not mask the original error. // NextDevStat |= VSTAT_FAILED; Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat); DEBUG ((DEBUG_INFO, "%a:%d: ERROR: %r\n", __func__, __LINE__, Status)); return Status; // reached only via Failed above } STATIC VOID EFIAPI VirtioSerialUninit ( IN OUT VIRTIO_SERIAL_DEV *Dev ) { UINT32 PortId; gBS->CloseEvent (Dev->Timer); // // Reset the virtual device -- see virtio-0.9.5, 2.2.2.1 Device Status. When // VIRTIO_CFG_WRITE() returns, the host will have learned to stay away from // the old comms area. // Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, 0); for (PortId = 0; PortId < MAX_PORTS; PortId++) { VirtioSerialPortRemove (Dev, PortId); } VirtioSerialUninitAllRings (Dev); } // // Event notification function enqueued by ExitBootServices(). // STATIC VOID EFIAPI VirtioSerialExitBoot ( IN EFI_EVENT Event, IN VOID *Context ) { VIRTIO_SERIAL_DEV *Dev; DEBUG ((DEBUG_INFO, "%a: Context=0x%p\n", __func__, Context)); // // Reset the device. This causes the hypervisor to forget about the virtio // ring. // // We allocated said ring in EfiBootServicesData type memory, and code // executing after ExitBootServices() is permitted to overwrite it. // Dev = Context; Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, 0); } STATIC VIRTIO_SERIAL_DEV * VirtioSerialFind ( EFI_HANDLE DeviceHandle ) { VIRTIO_SERIAL_DEV *Dev; LIST_ENTRY *Entry; BASE_LIST_FOR_EACH (Entry, &mVirtioSerialList) { Dev = CR (Entry, VIRTIO_SERIAL_DEV, Link, VIRTIO_SERIAL_SIG); if (DeviceHandle == Dev->DeviceHandle) { return Dev; } } return NULL; } // // Probe, start and stop functions of this driver, called by the DXE core for // specific devices. // // The following specifications document these interfaces: // - Driver Writer's Guide for UEFI 2.3.1 v1.01, 9 Driver Binding Protocol // - UEFI Spec 2.3.1 + Errata C, 10.1 EFI Driver Binding Protocol // // The implementation follows: // - Driver Writer's Guide for UEFI 2.3.1 v1.01 // - 5.1.3.4 OpenProtocol() and CloseProtocol() // - UEFI Spec 2.3.1 + Errata C // - 6.3 Protocol Handler Services // STATIC EFI_STATUS EFIAPI VirtioSerialDriverBindingSupported ( IN EFI_DRIVER_BINDING_PROTOCOL *This, IN EFI_HANDLE DeviceHandle, IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath ) { EFI_STATUS Status; VIRTIO_DEVICE_PROTOCOL *VirtIo; // // Attempt to open the device with the VirtIo set of interfaces. On success, // the protocol is "instantiated" for the VirtIo device. Covers duplicate // open attempts (EFI_ALREADY_STARTED). // Status = gBS->OpenProtocol ( DeviceHandle, // candidate device &gVirtioDeviceProtocolGuid, // for generic VirtIo access (VOID **)&VirtIo, // handle to instantiate This->DriverBindingHandle, // requestor driver identity DeviceHandle, // ControllerHandle, according to // the UEFI Driver Model EFI_OPEN_PROTOCOL_BY_DRIVER // get exclusive VirtIo access to // the device; to be released ); if (EFI_ERROR (Status)) { return Status; } if (VirtIo->SubSystemDeviceId != VIRTIO_SUBSYSTEM_CONSOLE) { Status = EFI_UNSUPPORTED; } // // We needed VirtIo access only transitorily, to see whether we support the // device or not. // gBS->CloseProtocol ( DeviceHandle, &gVirtioDeviceProtocolGuid, This->DriverBindingHandle, DeviceHandle ); return Status; } STATIC EFI_STATUS EFIAPI VirtioSerialDriverBindingStart ( IN EFI_DRIVER_BINDING_PROTOCOL *This, IN EFI_HANDLE DeviceHandle, IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath ) { VIRTIO_SERIAL_DEV *Dev; EFI_STATUS Status; Dev = (VIRTIO_SERIAL_DEV *)AllocateZeroPool (sizeof *Dev); if (Dev == NULL) { return EFI_OUT_OF_RESOURCES; } Status = gBS->OpenProtocol ( DeviceHandle, &gEfiDevicePathProtocolGuid, (VOID **)&Dev->DevicePath, This->DriverBindingHandle, DeviceHandle, EFI_OPEN_PROTOCOL_GET_PROTOCOL ); if (EFI_ERROR (Status)) { goto FreeVirtioSerial; } Status = gBS->OpenProtocol ( DeviceHandle, &gVirtioDeviceProtocolGuid, (VOID **)&Dev->VirtIo, This->DriverBindingHandle, DeviceHandle, EFI_OPEN_PROTOCOL_BY_DRIVER ); if (EFI_ERROR (Status)) { goto FreeVirtioSerial; } LogDevicePath (DEBUG_INFO, __func__, L"Dev", Dev->DevicePath); // // VirtIo access granted, configure virtio-serial device. // Dev->Signature = VIRTIO_SERIAL_SIG; Dev->DriverBindingHandle = This->DriverBindingHandle; Dev->DeviceHandle = DeviceHandle; Status = VirtioSerialInit (Dev); if (EFI_ERROR (Status)) { goto CloseVirtIo; } Status = gBS->CreateEvent ( EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_CALLBACK, &VirtioSerialExitBoot, Dev, &Dev->ExitBoot ); if (EFI_ERROR (Status)) { goto UninitDev; } InsertTailList (&mVirtioSerialList, &(Dev->Link)); return EFI_SUCCESS; UninitDev: VirtioSerialUninit (Dev); CloseVirtIo: gBS->CloseProtocol ( DeviceHandle, &gVirtioDeviceProtocolGuid, This->DriverBindingHandle, DeviceHandle ); FreeVirtioSerial: FreePool (Dev); return Status; } STATIC EFI_STATUS EFIAPI VirtioSerialDriverBindingStop ( IN EFI_DRIVER_BINDING_PROTOCOL *This, IN EFI_HANDLE DeviceHandle, IN UINTN NumberOfChildren, IN EFI_HANDLE *ChildHandleBuffer ) { VIRTIO_SERIAL_DEV *Dev; UINT32 PortId; UINT32 Child; Dev = VirtioSerialFind (DeviceHandle); if (!Dev) { return EFI_SUCCESS; } if (NumberOfChildren) { for (Child = 0; Child < NumberOfChildren; Child++) { DEBUG ((DEBUG_INFO, "%a:%d: child handle 0x%x\n", __func__, __LINE__, ChildHandleBuffer[Child])); for (PortId = 0; PortId < MAX_PORTS; PortId++) { if (Dev->Ports[PortId].Ready && Dev->Ports[PortId].SerialIo && (Dev->Ports[PortId].SerialIo->DeviceHandle == ChildHandleBuffer[Child])) { VirtioSerialPortRemove (Dev, PortId); } } } return EFI_SUCCESS; } DEBUG ((DEBUG_INFO, "%a:%d: controller handle 0x%x\n", __func__, __LINE__, DeviceHandle)); gBS->CloseEvent (Dev->ExitBoot); VirtioSerialUninit (Dev); gBS->CloseProtocol ( DeviceHandle, &gVirtioDeviceProtocolGuid, This->DriverBindingHandle, DeviceHandle ); RemoveEntryList (&(Dev->Link)); ZeroMem (Dev, sizeof (*Dev)); FreePool (Dev); return EFI_SUCCESS; } // // The static object that groups the Supported() (ie. probe), Start() and // Stop() functions of the driver together. Refer to UEFI Spec 2.3.1 + Errata // C, 10.1 EFI Driver Binding Protocol. // STATIC EFI_DRIVER_BINDING_PROTOCOL gDriverBinding = { &VirtioSerialDriverBindingSupported, &VirtioSerialDriverBindingStart, &VirtioSerialDriverBindingStop, 0x10, // Version, must be in [0x10 .. 0xFFFFFFEF] for IHV-developed drivers NULL, // ImageHandle, to be overwritten by // EfiLibInstallDriverBindingComponentName2() in VirtioSerialEntryPoint() NULL // DriverBindingHandle, ditto }; // // The purpose of the following scaffolding (EFI_COMPONENT_NAME_PROTOCOL and // EFI_COMPONENT_NAME2_PROTOCOL implementation) is to format the driver's name // in English, for display on standard console devices. This is recommended for // UEFI drivers that follow the UEFI Driver Model. Refer to the Driver Writer's // Guide for UEFI 2.3.1 v1.01, 11 UEFI Driver and Controller Names. // STATIC EFI_UNICODE_STRING_TABLE mDriverNameTable[] = { { "eng;en", L"Virtio Serial Driver" }, { NULL, NULL } }; STATIC EFI_UNICODE_STRING_TABLE mDeviceNameTable[] = { { "eng;en", L"Virtio Serial Device" }, { NULL, NULL } }; STATIC EFI_UNICODE_STRING_TABLE mPortNameTable[] = { { "eng;en", L"Virtio Serial Port" }, { NULL, NULL } }; STATIC EFI_COMPONENT_NAME_PROTOCOL gComponentName; STATIC EFI_STATUS EFIAPI VirtioSerialGetDriverName ( IN EFI_COMPONENT_NAME_PROTOCOL *This, IN CHAR8 *Language, OUT CHAR16 **DriverName ) { return LookupUnicodeString2 ( Language, This->SupportedLanguages, mDriverNameTable, DriverName, (BOOLEAN)(This == &gComponentName) // Iso639Language ); } STATIC EFI_STATUS EFIAPI VirtioSerialGetDeviceName ( IN EFI_COMPONENT_NAME_PROTOCOL *This, IN EFI_HANDLE DeviceHandle, IN EFI_HANDLE ChildHandle, IN CHAR8 *Language, OUT CHAR16 **ControllerName ) { EFI_UNICODE_STRING_TABLE *Table; VIRTIO_SERIAL_DEV *Dev; UINT32 PortId; Dev = VirtioSerialFind (DeviceHandle); if (!Dev) { return EFI_UNSUPPORTED; } if (ChildHandle) { for (PortId = 0; PortId < MAX_PORTS; PortId++) { if (Dev->Ports[PortId].Ready && Dev->Ports[PortId].SerialIo && (Dev->Ports[PortId].SerialIo->DeviceHandle == ChildHandle)) { *ControllerName = Dev->Ports[PortId].Name; return EFI_SUCCESS; } } Table = mPortNameTable; } else { Table = mDeviceNameTable; } return LookupUnicodeString2 ( Language, This->SupportedLanguages, Table, ControllerName, (BOOLEAN)(This == &gComponentName) ); } STATIC EFI_COMPONENT_NAME_PROTOCOL gComponentName = { &VirtioSerialGetDriverName, &VirtioSerialGetDeviceName, "eng" // SupportedLanguages, ISO 639-2 language codes }; STATIC EFI_COMPONENT_NAME2_PROTOCOL gComponentName2 = { (EFI_COMPONENT_NAME2_GET_DRIVER_NAME)&VirtioSerialGetDriverName, (EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME)&VirtioSerialGetDeviceName, "en" // SupportedLanguages, RFC 4646 language codes }; // // Entry point of this driver. // EFI_STATUS EFIAPI VirtioSerialEntryPoint ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { InitializeListHead (&mVirtioSerialList); return EfiLibInstallDriverBindingComponentName2 ( ImageHandle, SystemTable, &gDriverBinding, ImageHandle, &gComponentName, &gComponentName2 ); }