| /** @file | |
| Driver for virtio-serial devices. | |
| Helper functions to manage virtio serial ports. | |
| Console ports will be registered as SerialIo UARTs. | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include <Library/BaseMemoryLib.h> | |
| #include <Library/DebugLib.h> | |
| #include <Library/DevicePathLib.h> | |
| #include <Library/MemoryAllocationLib.h> | |
| #include <Library/PrintLib.h> | |
| #include <Library/UefiBootServicesTableLib.h> | |
| #include <Library/UefiLib.h> | |
| #include <Library/VirtioLib.h> | |
| #include "VirtioSerial.h" | |
| ACPI_HID_DEVICE_PATH mAcpiSerialDevNode = { | |
| { | |
| ACPI_DEVICE_PATH, | |
| ACPI_DP, | |
| { | |
| (UINT8)(sizeof (ACPI_HID_DEVICE_PATH)), | |
| (UINT8)((sizeof (ACPI_HID_DEVICE_PATH)) >> 8) | |
| }, | |
| }, | |
| EISA_PNP_ID (0x0501), | |
| 0 | |
| }; | |
| UART_DEVICE_PATH mUartDevNode = { | |
| { | |
| MESSAGING_DEVICE_PATH, | |
| MSG_UART_DP, | |
| { | |
| (UINT8)(sizeof (UART_DEVICE_PATH)), | |
| (UINT8)((sizeof (UART_DEVICE_PATH)) >> 8) | |
| } | |
| }, | |
| 0, // Reserved | |
| 115200, // Speed | |
| 8, 1, 1 // 8n1 | |
| }; | |
| STATIC | |
| UINT16 | |
| PortRx ( | |
| IN UINT32 PortId | |
| ) | |
| { | |
| ASSERT (PortId < MAX_PORTS); | |
| if (PortId >= 1) { | |
| return (UINT16)(VIRTIO_SERIAL_Q_RX_BASE + (PortId - 1) * 2); | |
| } | |
| return VIRTIO_SERIAL_Q_RX_PORT0; | |
| } | |
| STATIC | |
| UINT16 | |
| PortTx ( | |
| IN UINT32 PortId | |
| ) | |
| { | |
| ASSERT (PortId < MAX_PORTS); | |
| if (PortId >= 1) { | |
| return (UINT16)(VIRTIO_SERIAL_Q_TX_BASE + (PortId - 1) * 2); | |
| } | |
| return VIRTIO_SERIAL_Q_TX_PORT0; | |
| } | |
| STATIC | |
| EFI_STATUS | |
| EFIAPI | |
| VirtioSerialIoReset ( | |
| IN EFI_SERIAL_IO_PROTOCOL *This | |
| ) | |
| { | |
| DEBUG ((DEBUG_VERBOSE, "%a:%d:\n", __func__, __LINE__)); | |
| return EFI_SUCCESS; | |
| } | |
| STATIC | |
| EFI_STATUS | |
| EFIAPI | |
| VirtioSerialIoSetAttributes ( | |
| IN EFI_SERIAL_IO_PROTOCOL *This, | |
| IN UINT64 BaudRate, | |
| IN UINT32 ReceiveFifoDepth, | |
| IN UINT32 Timeout, | |
| IN EFI_PARITY_TYPE Parity, | |
| IN UINT8 DataBits, | |
| IN EFI_STOP_BITS_TYPE StopBits | |
| ) | |
| { | |
| DEBUG (( | |
| DEBUG_VERBOSE, | |
| "%a:%d: Rate %ld, Fifo %d, Bits %d\n", | |
| __func__, | |
| __LINE__, | |
| BaudRate, | |
| ReceiveFifoDepth, | |
| DataBits | |
| )); | |
| return EFI_SUCCESS; | |
| } | |
| STATIC | |
| EFI_STATUS | |
| EFIAPI | |
| VirtioSerialIoSetControl ( | |
| IN EFI_SERIAL_IO_PROTOCOL *This, | |
| IN UINT32 Control | |
| ) | |
| { | |
| DEBUG ((DEBUG_INFO, "%a:%d: Control 0x%x\n", __func__, __LINE__, Control)); | |
| return EFI_SUCCESS; | |
| } | |
| STATIC | |
| EFI_STATUS | |
| EFIAPI | |
| VirtioSerialIoGetControl ( | |
| IN EFI_SERIAL_IO_PROTOCOL *This, | |
| OUT UINT32 *Control | |
| ) | |
| { | |
| DEBUG ((DEBUG_VERBOSE, "%a:%d: Control 0x%x\n", __func__, __LINE__, *Control)); | |
| return EFI_SUCCESS; | |
| } | |
| STATIC | |
| EFI_STATUS | |
| EFIAPI | |
| VirtioSerialIoWrite ( | |
| IN EFI_SERIAL_IO_PROTOCOL *This, | |
| IN OUT UINTN *BufferSize, | |
| IN VOID *Buffer | |
| ) | |
| { | |
| VIRTIO_SERIAL_IO_PROTOCOL *SerialIo = (VIRTIO_SERIAL_IO_PROTOCOL *)This; | |
| VIRTIO_SERIAL_PORT *Port = SerialIo->Dev->Ports + SerialIo->PortId; | |
| UINT32 Length; | |
| EFI_TPL OldTpl; | |
| if (!Port->DeviceOpen) { | |
| *BufferSize = 0; | |
| return EFI_SUCCESS; | |
| } | |
| VirtioSerialRingClearTx (SerialIo->Dev, PortTx (SerialIo->PortId)); | |
| OldTpl = gBS->RaiseTPL (TPL_NOTIFY); | |
| if (SerialIo->WriteOffset && | |
| (SerialIo->WriteOffset + *BufferSize > PORT_TX_BUFSIZE)) | |
| { | |
| DEBUG ((DEBUG_VERBOSE, "%a:%d: WriteFlush %d\n", __func__, __LINE__, SerialIo->WriteOffset)); | |
| VirtioSerialRingSendBuffer ( | |
| SerialIo->Dev, | |
| PortTx (SerialIo->PortId), | |
| SerialIo->WriteBuffer, | |
| SerialIo->WriteOffset, | |
| TRUE | |
| ); | |
| SerialIo->WriteOffset = 0; | |
| } | |
| Length = MIN ((UINT32)(*BufferSize), PORT_TX_BUFSIZE - SerialIo->WriteOffset); | |
| CopyMem (SerialIo->WriteBuffer + SerialIo->WriteOffset, Buffer, Length); | |
| SerialIo->WriteOffset += Length; | |
| *BufferSize = Length; | |
| gBS->RestoreTPL (OldTpl); | |
| return EFI_SUCCESS; | |
| } | |
| STATIC | |
| EFI_STATUS | |
| EFIAPI | |
| VirtioSerialIoRead ( | |
| IN EFI_SERIAL_IO_PROTOCOL *This, | |
| IN OUT UINTN *BufferSize, | |
| OUT VOID *Buffer | |
| ) | |
| { | |
| VIRTIO_SERIAL_IO_PROTOCOL *SerialIo = (VIRTIO_SERIAL_IO_PROTOCOL *)This; | |
| VIRTIO_SERIAL_PORT *Port = SerialIo->Dev->Ports + SerialIo->PortId; | |
| BOOLEAN HasData; | |
| UINT32 Length; | |
| EFI_TPL OldTpl; | |
| if (!Port->DeviceOpen) { | |
| goto NoData; | |
| } | |
| OldTpl = gBS->RaiseTPL (TPL_NOTIFY); | |
| if (SerialIo->WriteOffset) { | |
| DEBUG ((DEBUG_VERBOSE, "%a:%d: WriteFlush %d\n", __func__, __LINE__, SerialIo->WriteOffset)); | |
| VirtioSerialRingSendBuffer ( | |
| SerialIo->Dev, | |
| PortTx (SerialIo->PortId), | |
| SerialIo->WriteBuffer, | |
| SerialIo->WriteOffset, | |
| TRUE | |
| ); | |
| SerialIo->WriteOffset = 0; | |
| } | |
| gBS->RestoreTPL (OldTpl); | |
| if (SerialIo->ReadOffset == SerialIo->ReadSize) { | |
| HasData = VirtioSerialRingGetBuffer ( | |
| SerialIo->Dev, | |
| PortRx (SerialIo->PortId), | |
| &SerialIo->ReadBuffer, | |
| &SerialIo->ReadSize | |
| ); | |
| if (!HasData) { | |
| goto NoData; | |
| } | |
| SerialIo->ReadOffset = 0; | |
| } | |
| if (SerialIo->ReadOffset < SerialIo->ReadSize) { | |
| Length = SerialIo->ReadSize - SerialIo->ReadOffset; | |
| if (Length > *BufferSize) { | |
| Length = (UINT32)(*BufferSize); | |
| } | |
| CopyMem (Buffer, SerialIo->ReadBuffer + SerialIo->ReadOffset, Length); | |
| SerialIo->ReadOffset += Length; | |
| *BufferSize = Length; | |
| return EFI_SUCCESS; | |
| } | |
| NoData: | |
| *BufferSize = 0; | |
| return EFI_SUCCESS; | |
| } | |
| STATIC | |
| EFI_STATUS | |
| EFIAPI | |
| VirtioSerialIoInit ( | |
| IN OUT VIRTIO_SERIAL_DEV *Dev, | |
| IN UINT32 PortId | |
| ) | |
| { | |
| VIRTIO_SERIAL_PORT *Port = Dev->Ports + PortId; | |
| VIRTIO_SERIAL_IO_PROTOCOL *SerialIo; | |
| EFI_STATUS Status; | |
| SerialIo = (VIRTIO_SERIAL_IO_PROTOCOL *)AllocateZeroPool (sizeof *SerialIo); | |
| Port->SerialIo = SerialIo; | |
| SerialIo->SerialIo.Revision = EFI_SERIAL_IO_PROTOCOL_REVISION; | |
| SerialIo->SerialIo.Reset = VirtioSerialIoReset; | |
| SerialIo->SerialIo.SetAttributes = VirtioSerialIoSetAttributes; | |
| SerialIo->SerialIo.SetControl = VirtioSerialIoSetControl; | |
| SerialIo->SerialIo.GetControl = VirtioSerialIoGetControl; | |
| SerialIo->SerialIo.Write = VirtioSerialIoWrite; | |
| SerialIo->SerialIo.Read = VirtioSerialIoRead; | |
| SerialIo->SerialIo.Mode = &SerialIo->SerialIoMode; | |
| SerialIo->Dev = Dev; | |
| SerialIo->PortId = PortId; | |
| SerialIo->DevicePath = DuplicateDevicePath (Dev->DevicePath); | |
| mAcpiSerialDevNode.UID = PortId; | |
| SerialIo->DevicePath = AppendDevicePathNode ( | |
| SerialIo->DevicePath, | |
| (EFI_DEVICE_PATH_PROTOCOL *)&mAcpiSerialDevNode | |
| ); | |
| SerialIo->DevicePath = AppendDevicePathNode ( | |
| SerialIo->DevicePath, | |
| (EFI_DEVICE_PATH_PROTOCOL *)&mUartDevNode | |
| ); | |
| LogDevicePath (DEBUG_INFO, __func__, L"UART", SerialIo->DevicePath); | |
| Status = gBS->InstallMultipleProtocolInterfaces ( | |
| &SerialIo->DeviceHandle, | |
| &gEfiDevicePathProtocolGuid, | |
| SerialIo->DevicePath, | |
| &gEfiSerialIoProtocolGuid, | |
| &SerialIo->SerialIo, | |
| NULL | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((DEBUG_INFO, "%a:%d: ERROR: %r\n", __func__, __LINE__, Status)); | |
| goto FreeSerialIo; | |
| } | |
| Status = gBS->OpenProtocol ( | |
| Dev->DeviceHandle, | |
| &gVirtioDeviceProtocolGuid, | |
| (VOID **)&Dev->VirtIo, | |
| Dev->DriverBindingHandle, | |
| SerialIo->DeviceHandle, | |
| EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((DEBUG_INFO, "%a:%d: ERROR: %r\n", __func__, __LINE__, Status)); | |
| goto UninstallProtocol; | |
| } | |
| return EFI_SUCCESS; | |
| UninstallProtocol: | |
| gBS->UninstallMultipleProtocolInterfaces ( | |
| SerialIo->DeviceHandle, | |
| &gEfiDevicePathProtocolGuid, | |
| SerialIo->DevicePath, | |
| &gEfiSerialIoProtocolGuid, | |
| &SerialIo->SerialIo, | |
| NULL | |
| ); | |
| FreeSerialIo: | |
| FreePool (Port->SerialIo); | |
| Port->SerialIo = NULL; | |
| return Status; | |
| } | |
| STATIC | |
| VOID | |
| EFIAPI | |
| VirtioSerialIoUninit ( | |
| VIRTIO_SERIAL_IO_PROTOCOL *SerialIo | |
| ) | |
| { | |
| VIRTIO_SERIAL_DEV *Dev = SerialIo->Dev; | |
| VIRTIO_SERIAL_PORT *Port = Dev->Ports + SerialIo->PortId; | |
| DEBUG ((DEBUG_INFO, "%a:%d: %s\n", __func__, __LINE__, Port->Name)); | |
| gBS->CloseProtocol ( | |
| Dev->DeviceHandle, | |
| &gVirtioDeviceProtocolGuid, | |
| Dev->DriverBindingHandle, | |
| SerialIo->DeviceHandle | |
| ); | |
| gBS->UninstallMultipleProtocolInterfaces ( | |
| SerialIo->DeviceHandle, | |
| &gEfiDevicePathProtocolGuid, | |
| SerialIo->DevicePath, | |
| &gEfiSerialIoProtocolGuid, | |
| &SerialIo->SerialIo, | |
| NULL | |
| ); | |
| FreePool (SerialIo); | |
| Port->SerialIo = NULL; | |
| } | |
| EFI_STATUS | |
| EFIAPI | |
| VirtioSerialPortAdd ( | |
| IN OUT VIRTIO_SERIAL_DEV *Dev, | |
| IN UINT32 PortId | |
| ) | |
| { | |
| VIRTIO_SERIAL_PORT *Port = Dev->Ports + PortId; | |
| EFI_STATUS Status; | |
| if (Port->Ready) { | |
| return EFI_SUCCESS; | |
| } | |
| Status = VirtioSerialInitRing (Dev, PortRx (PortId), PORT_RX_BUFSIZE); | |
| if (EFI_ERROR (Status)) { | |
| goto Failed; | |
| } | |
| Status = VirtioSerialInitRing (Dev, PortTx (PortId), PORT_TX_BUFSIZE); | |
| if (EFI_ERROR (Status)) { | |
| goto Failed; | |
| } | |
| UnicodeSPrint (Port->Name, sizeof (Port->Name), L"Port #%d", PortId); | |
| VirtioSerialRingFillRx (Dev, PortRx (PortId)); | |
| Port->Ready = TRUE; | |
| return EFI_SUCCESS; | |
| Failed: | |
| VirtioSerialUninitRing (Dev, PortRx (PortId)); | |
| return Status; | |
| } | |
| VOID | |
| EFIAPI | |
| VirtioSerialPortSetConsole ( | |
| IN OUT VIRTIO_SERIAL_DEV *Dev, | |
| IN UINT32 PortId | |
| ) | |
| { | |
| VIRTIO_SERIAL_PORT *Port = Dev->Ports + PortId; | |
| Port->Console = TRUE; | |
| UnicodeSPrint (Port->Name, sizeof (Port->Name), L"Console #%d", PortId); | |
| VirtioSerialIoInit (Dev, PortId); | |
| } | |
| VOID | |
| EFIAPI | |
| VirtioSerialPortSetName ( | |
| IN OUT VIRTIO_SERIAL_DEV *Dev, | |
| IN UINT32 PortId, | |
| IN UINT8 *Name | |
| ) | |
| { | |
| VIRTIO_SERIAL_PORT *Port = Dev->Ports + PortId; | |
| DEBUG ((DEBUG_INFO, "%a:%d: \"%a\"\n", __func__, __LINE__, Name)); | |
| UnicodeSPrint (Port->Name, sizeof (Port->Name), L"NamedPort #%d (%a)", PortId, Name); | |
| } | |
| VOID | |
| EFIAPI | |
| VirtioSerialPortSetDeviceOpen ( | |
| IN OUT VIRTIO_SERIAL_DEV *Dev, | |
| IN UINT32 PortId, | |
| IN UINT16 Value | |
| ) | |
| { | |
| VIRTIO_SERIAL_PORT *Port = Dev->Ports + PortId; | |
| Port->DeviceOpen = (BOOLEAN)Value; | |
| if (Port->DeviceOpen) { | |
| VirtioSerialTxControl (Dev, PortId, VIRTIO_SERIAL_PORT_OPEN, 1); | |
| } | |
| } | |
| VOID | |
| EFIAPI | |
| VirtioSerialPortRemove ( | |
| IN OUT VIRTIO_SERIAL_DEV *Dev, | |
| IN UINT32 PortId | |
| ) | |
| { | |
| VIRTIO_SERIAL_PORT *Port = Dev->Ports + PortId; | |
| if (!Port->Ready) { | |
| return; | |
| } | |
| if (Port->SerialIo) { | |
| VirtioSerialIoUninit (Port->SerialIo); | |
| Port->SerialIo = NULL; | |
| } | |
| VirtioSerialUninitRing (Dev, PortRx (PortId)); | |
| VirtioSerialUninitRing (Dev, PortTx (PortId)); | |
| Port->Ready = FALSE; | |
| } |