From patchwork Wed Apr 6 12:40:25 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laszlo Ersek X-Patchwork-Id: 65180 Delivered-To: patch@linaro.org Received: by 10.112.199.169 with SMTP id jl9csp1027809lbc; Wed, 6 Apr 2016 05:41:29 -0700 (PDT) X-Received: by 10.98.42.211 with SMTP id q202mr37618433pfq.13.1459946473816; Wed, 06 Apr 2016 05:41:13 -0700 (PDT) Return-Path: Received: from ml01.01.org (ml01.01.org. [2001:19d0:306:5::1]) by mx.google.com with ESMTPS id kv12si4409494pab.194.2016.04.06.05.41.13 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 06 Apr 2016 05:41:13 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of edk2-devel-bounces@lists.01.org designates 2001:19d0:306:5::1 as permitted sender) client-ip=2001:19d0:306:5::1; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of edk2-devel-bounces@lists.01.org designates 2001:19d0:306:5::1 as permitted sender) smtp.mailfrom=edk2-devel-bounces@lists.01.org Received: from [127.0.0.1] (localhost [IPv6:::1]) by ml01.01.org (Postfix) with ESMTP id AE32F1A1FDC; Wed, 6 Apr 2016 05:41:00 -0700 (PDT) X-Original-To: edk2-devel@ml01.01.org Delivered-To: edk2-devel@ml01.01.org Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ml01.01.org (Postfix) with ESMTPS id 933971A1FDA for ; Wed, 6 Apr 2016 05:40:59 -0700 (PDT) Received: from int-mx11.intmail.prod.int.phx2.redhat.com (int-mx11.intmail.prod.int.phx2.redhat.com [10.5.11.24]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 2FE4D12B48; Wed, 6 Apr 2016 12:40:59 +0000 (UTC) Received: from lacos-laptop-7.usersys.redhat.com ([10.3.113.15]) by int-mx11.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u36CeV8a027635; Wed, 6 Apr 2016 08:40:58 -0400 From: Laszlo Ersek To: edk2-devel-01 Date: Wed, 6 Apr 2016 14:40:25 +0200 Message-Id: <1459946427-15771-16-git-send-email-lersek@redhat.com> In-Reply-To: <1459946427-15771-1-git-send-email-lersek@redhat.com> References: <1459946427-15771-1-git-send-email-lersek@redhat.com> X-Scanned-By: MIMEDefang 2.68 on 10.5.11.24 Subject: [edk2] [wave 3 PATCH v2 15/17] OvmfPkg: Virtio10Dxe: non-transitional driver for virtio-1.0 PCI devices X-BeenThere: edk2-devel@lists.01.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: EDK II Development List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Cc: Jordan Justen , Ard Biesheuvel MIME-Version: 1.0 Errors-To: edk2-devel-bounces@lists.01.org Sender: "edk2-devel" This driver implements the VIRTIO_DEVICE_PROTOCOL for non-transitional PCI devices, based on the virtio-1.0 specification (csprd05). Non-transitional means that it only binds QEMU's virtio-xxx-pci devices that receive the ",disable-legacy=on,disable-modern=off" properties on the QEMU command line. These devices have distinct PCI Device IDs from those that are bound by VirtioPciDeviceDxe. The central abstraction of this driver is the VIRTIO_1_0_CONFIG type. It is practically a "fat pointer" to a register block. The pointed-to register block - may or may not exist (the latter being mostly useful for virtio-1.0 devices that have no device-specific registers), - lives in one of the device's BARs, - lives in an IO or MMIO BAR, - lives at an offset relative to the BAR start, - has its size also maintained. Such VIRTIO_1_0_CONFIG "fat pointers" (i.e., the locations of the register blocks) are parsed from vendor capabilities that reside in the device's standard PCI capabilities list (in PCI config space). Cc: Ard Biesheuvel Cc: Jordan Justen Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Laszlo Ersek Tested-by: Ard Biesheuvel Acked-by: Jordan Justen --- Notes: v2: - no need to include explicitly, exposes all versions [Jordan] OvmfPkg/Virtio10Dxe/Virtio10.inf | 40 + OvmfPkg/Virtio10Dxe/Virtio10.h | 56 ++ OvmfPkg/Virtio10Dxe/Virtio10.c | 1061 ++++++++++++++++++++ 3 files changed, 1157 insertions(+) -- 1.8.3.1 _______________________________________________ edk2-devel mailing list edk2-devel@lists.01.org https://lists.01.org/mailman/listinfo/edk2-devel diff --git a/OvmfPkg/Virtio10Dxe/Virtio10.inf b/OvmfPkg/Virtio10Dxe/Virtio10.inf new file mode 100644 index 000000000000..f868d1cfc449 --- /dev/null +++ b/OvmfPkg/Virtio10Dxe/Virtio10.inf @@ -0,0 +1,40 @@ +## @file +# A non-transitional driver for VirtIo 1.0 PCI devices. +# +# Copyright (C) 2016, Red Hat, Inc. +# +# This program and the accompanying materials are licensed and made available +# under the terms and conditions of the BSD License which accompanies this +# distribution. The full text of the license may be found at +# http://opensource.org/licenses/bsd-license.php +# +# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT +# WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = Virtio10 + FILE_GUID = 0170F60C-1D40-4651-956D-F0BD9879D527 + MODULE_TYPE = UEFI_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = Virtio10EntryPoint + +[Sources] + Virtio10.c + +[Packages] + MdePkg/MdePkg.dec + OvmfPkg/OvmfPkg.dec + +[LibraryClasses] + BaseMemoryLib + DebugLib + MemoryAllocationLib + UefiBootServicesTableLib + UefiDriverEntryPoint + UefiLib + +[Protocols] + gEfiPciIoProtocolGuid ## TO_START + gVirtioDeviceProtocolGuid ## BY_START diff --git a/OvmfPkg/Virtio10Dxe/Virtio10.h b/OvmfPkg/Virtio10Dxe/Virtio10.h new file mode 100644 index 000000000000..2fbe27f1cfee --- /dev/null +++ b/OvmfPkg/Virtio10Dxe/Virtio10.h @@ -0,0 +1,56 @@ +/** @file + Private definitions of the VirtIo 1.0 driver. + + Copyright (C) 2016, Red Hat, Inc. + + This program and the accompanying materials are licensed and made available + under the terms and conditions of the BSD License which accompanies this + distribution. The full text of the license may be found at + http://opensource.org/licenses/bsd-license.php + + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT + WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +**/ + +#ifndef _VIRTIO_1_0_DXE_H_ +#define _VIRTIO_1_0_DXE_H_ + +#include +#include + +#define VIRTIO_1_0_SIGNATURE SIGNATURE_32 ('V', 'I', 'O', '1') + +// +// Type of the PCI BAR that contains a VirtIo 1.0 config structure. +// +typedef enum { + Virtio10BarTypeMem, + Virtio10BarTypeIo +} VIRTIO_1_0_BAR_TYPE; + +// +// The type below defines the access to a VirtIo 1.0 config structure. +// +typedef struct { + BOOLEAN Exists; // The device exposes this structure + VIRTIO_1_0_BAR_TYPE BarType; + UINT8 Bar; + UINT32 Offset; // Offset into BAR where structure starts + UINT32 Length; // Length of structure in BAR. +} VIRTIO_1_0_CONFIG; + +typedef struct { + UINT32 Signature; + VIRTIO_DEVICE_PROTOCOL VirtIo; + EFI_PCI_IO_PROTOCOL *PciIo; + UINT64 OriginalPciAttributes; + VIRTIO_1_0_CONFIG CommonConfig; // Common settings + VIRTIO_1_0_CONFIG NotifyConfig; // Notifications + UINT32 NotifyOffsetMultiplier; + VIRTIO_1_0_CONFIG SpecificConfig; // Device specific settings +} VIRTIO_1_0_DEV; + +#define VIRTIO_1_0_FROM_VIRTIO_DEVICE(Device) \ + CR (Device, VIRTIO_1_0_DEV, VirtIo, VIRTIO_1_0_SIGNATURE) + +#endif // _VIRTIO_1_0_DXE_H_ diff --git a/OvmfPkg/Virtio10Dxe/Virtio10.c b/OvmfPkg/Virtio10Dxe/Virtio10.c new file mode 100644 index 000000000000..06f069907753 --- /dev/null +++ b/OvmfPkg/Virtio10Dxe/Virtio10.c @@ -0,0 +1,1061 @@ +/** @file + A non-transitional driver for VirtIo 1.0 PCI devices. + + Copyright (C) 2016, Red Hat, Inc. + + This program and the accompanying materials are licensed and made available + under the terms and conditions of the BSD License which accompanies this + distribution. The full text of the license may be found at + http://opensource.org/licenses/bsd-license.php + + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT + WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +**/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Virtio10.h" + + +// +// Utility functions +// + +/** + Transfer data between the caller and a register in a virtio-1.0 register + block. + + @param[in] PciIo The EFI_PCI_IO_PROTOCOL instance that represents + the device. + + @param[in] Config The "fat pointer" structure that identifies the + register block to access. + + @param[in] Write TRUE if the register should be written, FALSE if + the register should be read. + + @param[in] FieldOffset The offset of the register within the register + block. + + @param[in] FieldSize The size of the register within the register + block. Can be one of 1, 2, 4 and 8. Accesses to + 8-byte registers are broken up into two 4-byte + accesses. + + @param[in,out] Buffer When Write is TRUE, the register is written with + data from Buffer. When Write is FALSE, the caller + receives the register value into Buffer. + + @retval EFI_SUCCESS Register access successful. + + @retval EFI_INVALID_PARAMETER The register block pointed-to by Config + doesn't exist; or FieldOffset and FieldSize + would overflow the register block; or + FieldSize is invalid. + + @return Error codes from + EFI_PCI_IO_PROTOCOL.(Io|Mem).(Read|Write) + member functions. +**/ +STATIC +EFI_STATUS +Virtio10Transfer ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN VIRTIO_1_0_CONFIG *Config, + IN BOOLEAN Write, + IN UINTN FieldOffset, + IN UINTN FieldSize, + IN OUT VOID *Buffer + ) +{ + UINTN Count; + EFI_PCI_IO_PROTOCOL_WIDTH Width; + EFI_PCI_IO_PROTOCOL_ACCESS *BarType; + EFI_PCI_IO_PROTOCOL_IO_MEM Access; + + if (!Config->Exists || + FieldSize > Config->Length || + FieldOffset > Config->Length - FieldSize) { + return EFI_INVALID_PARAMETER; + } + + Count = 1; + switch (FieldSize) { + case 1: + Width = EfiPciIoWidthUint8; + break; + + case 2: + Width = EfiPciIoWidthUint16; + break; + + case 8: + Count = 2; + // + // fall through + // + + case 4: + Width = EfiPciIoWidthUint32; + break; + + default: + return EFI_INVALID_PARAMETER; + } + + BarType = (Config->BarType == Virtio10BarTypeMem) ? &PciIo->Mem : &PciIo->Io; + Access = Write ? BarType->Write : BarType->Read; + + return Access (PciIo, Width, Config->Bar, Config->Offset + FieldOffset, + Count, Buffer); +} + + +/** + Determine if a PCI BAR is IO or MMIO. + + @param[in] PciIo The EFI_PCI_IO_PROTOCOL instance that represents the + device. + + @param[in] BarIndex The number of the BAR whose type the caller is + interested in. + + @param[out] BarType On output, a VIRTIO_1_0_BAR_TYPE value that gives the + type of the BAR. + + @retval EFI_SUCCESS The BAR type has been recognized and stored in + BarType. + + @retval EFI_UNSUPPORTED The BAR type couldn't be identified. + + @return Error codes from + EFI_PCI_IO_PROTOCOL.GetBarAttributes(). +**/ +STATIC +EFI_STATUS +GetBarType ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 BarIndex, + OUT VIRTIO_1_0_BAR_TYPE *BarType + ) +{ + EFI_STATUS Status; + VOID *Resources; + + Status = PciIo->GetBarAttributes (PciIo, BarIndex, NULL, &Resources); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = EFI_UNSUPPORTED; + + if (*(UINT8 *)Resources == ACPI_QWORD_ADDRESS_SPACE_DESCRIPTOR) { + EFI_ACPI_QWORD_ADDRESS_SPACE_DESCRIPTOR *Descriptor; + + Descriptor = Resources; + switch (Descriptor->ResType) { + case ACPI_ADDRESS_SPACE_TYPE_MEM: + *BarType = Virtio10BarTypeMem; + Status = EFI_SUCCESS; + break; + + case ACPI_ADDRESS_SPACE_TYPE_IO: + *BarType = Virtio10BarTypeIo; + Status = EFI_SUCCESS; + break; + + default: + break; + } + } + + FreePool (Resources); + return Status; +} + + +/** + Read a slice from PCI config space at the given offset, then advance the + offset. + + @param [in] PciIo The EFI_PCI_IO_PROTOCOL instance that represents the + device. + + @param [in,out] Offset On input, the offset in PCI config space to start + reading from. On output, the offset of the first byte + that was not read. On error, Offset is not modified. + + @param [in] Size The number of bytes to read. + + @param [out] Buffer On output, the bytes read from PCI config space are + stored in this object. + + @retval EFI_SUCCESS Size bytes have been transferred from PCI config space + (from Offset) to Buffer, and Offset has been incremented + by Size. + + @return Error codes from PciIo->Pci.Read(). +**/ +STATIC +EFI_STATUS +ReadConfigSpace ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN OUT UINT32 *Offset, + IN UINTN Size, + OUT VOID *Buffer + ) +{ + EFI_STATUS Status; + + Status = PciIo->Pci.Read (PciIo, EfiPciIoWidthUint8, *Offset, Size, Buffer); + if (EFI_ERROR (Status)) { + return Status; + } + *Offset += (UINT32)Size; + return EFI_SUCCESS; +} + + +/* + Traverse the PCI capabilities list of a virtio-1.0 device, and capture the + locations of the interesting virtio-1.0 register blocks. + + @param[in,out] Device The VIRTIO_1_0_DEV structure that identifies + the device. On input, the caller is responsible + that the Device->PciIo member be live, and that + the CommonConfig, NotifyConfig, + NotifyOffsetMultiplier and SpecificConfig + members be zeroed. On output, said members + will have been updated from the PCI + capabilities found. + + @param[in] CapabilityPtr The offset of the first capability in PCI + config space, taken from the standard PCI + device header. + + @retval EFI_SUCCESS Traversal successful. + + @return Error codes from the ReadConfigSpace() and GetBarType() + helper functions. +*/ +STATIC +EFI_STATUS +ParseCapabilities ( + IN OUT VIRTIO_1_0_DEV *Device, + IN UINT8 CapabilityPtr + ) +{ + UINT32 Offset; + VIRTIO_PCI_CAP_LINK CapLink; + + for (Offset = CapabilityPtr & 0xFC; + Offset > 0; + Offset = CapLink.CapNext & 0xFC + ) { + EFI_STATUS Status; + UINT8 CapLen; + VIRTIO_PCI_CAP VirtIoCap; + VIRTIO_1_0_CONFIG *ParsedConfig; + + // + // Read capability identifier and link to next capability. + // + Status = ReadConfigSpace (Device->PciIo, &Offset, sizeof CapLink, + &CapLink); + if (EFI_ERROR (Status)) { + return Status; + } + if (CapLink.CapId != 0x09) { + // + // Not a vendor-specific capability, move to the next one. + // + continue; + } + + // + // Big enough to accommodate a VIRTIO_PCI_CAP structure? + // + Status = ReadConfigSpace (Device->PciIo, &Offset, sizeof CapLen, &CapLen); + if (EFI_ERROR (Status)) { + return Status; + } + if (CapLen < sizeof CapLink + sizeof CapLen + sizeof VirtIoCap) { + // + // Too small, move to next. + // + continue; + } + + // + // Read interesting part of capability. + // + Status = ReadConfigSpace (Device->PciIo, &Offset, sizeof VirtIoCap, + &VirtIoCap); + if (EFI_ERROR (Status)) { + return Status; + } + switch (VirtIoCap.ConfigType) { + case VIRTIO_PCI_CAP_COMMON_CFG: + ParsedConfig = &Device->CommonConfig; + break; + case VIRTIO_PCI_CAP_NOTIFY_CFG: + ParsedConfig = &Device->NotifyConfig; + break; + case VIRTIO_PCI_CAP_DEVICE_CFG: + ParsedConfig = &Device->SpecificConfig; + break; + default: + // + // Capability is not interesting. + // + continue; + } + + // + // Save the location of the register block into ParsedConfig. + // + Status = GetBarType (Device->PciIo, VirtIoCap.Bar, &ParsedConfig->BarType); + if (EFI_ERROR (Status)) { + return Status; + } + ParsedConfig->Bar = VirtIoCap.Bar; + ParsedConfig->Offset = VirtIoCap.Offset; + ParsedConfig->Length = VirtIoCap.Length; + + if (VirtIoCap.ConfigType == VIRTIO_PCI_CAP_NOTIFY_CFG) { + // + // This capability has an additional field called NotifyOffsetMultiplier; + // parse it too. + // + if (CapLen < sizeof CapLink + sizeof CapLen + sizeof VirtIoCap + + sizeof Device->NotifyOffsetMultiplier) { + // + // Too small, move to next. + // + continue; + } + + Status = ReadConfigSpace (Device->PciIo, &Offset, + sizeof Device->NotifyOffsetMultiplier, + &Device->NotifyOffsetMultiplier); + if (EFI_ERROR (Status)) { + return Status; + } + } + + // + // Capability parsed successfully. + // + ParsedConfig->Exists = TRUE; + } + + return EFI_SUCCESS; +} + + +/** + Accumulate the BAR type of a virtio-1.0 register block into a UINT64 + attribute map, such that the latter is suitable for enabling IO / MMIO + decoding with EFI_PCI_IO_PROTOCOL.Attributes(). + + @param[in] Config The "fat pointer" structure that identifies the + register block. It is allowed for the register + block not to exist. + + @param[in,out] Attributes On output, if the register block exists, + EFI_PCI_IO_ATTRIBUTE_MEMORY or + EFI_PCI_IO_ATTRIBUTE_IO is OR-ed into Attributes, + according to the register block's BAR type. +**/ +STATIC +VOID +UpdateAttributes ( + IN VIRTIO_1_0_CONFIG *Config, + IN OUT UINT64 *Attributes + ) +{ + if (Config->Exists) { + *Attributes |= (Config->BarType == Virtio10BarTypeMem) ? + EFI_PCI_IO_ATTRIBUTE_MEMORY: + EFI_PCI_IO_ATTRIBUTE_IO; + } +} + + +// +// VIRTIO_DEVICE_PROTOCOL member functions +// + +STATIC +EFI_STATUS +EFIAPI +Virtio10GetDeviceFeatures ( + IN VIRTIO_DEVICE_PROTOCOL *This, + OUT UINT64 *DeviceFeatures + ) +{ + VIRTIO_1_0_DEV *Dev; + UINT32 Selector; + UINT32 Features32[2]; + + Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This); + + for (Selector = 0; Selector < 2; ++Selector) { + EFI_STATUS Status; + + // + // Select the low or high half of the features. + // + Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE, + OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DeviceFeatureSelect), + sizeof Selector, &Selector); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Fetch that half. + // + Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, FALSE, + OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DeviceFeature), + sizeof Features32[Selector], &Features32[Selector]); + if (EFI_ERROR (Status)) { + return Status; + } + } + + *DeviceFeatures = LShiftU64 (Features32[1], 32) | Features32[0]; + return EFI_SUCCESS; +} + + +STATIC +EFI_STATUS +EFIAPI +Virtio10SetGuestFeatures ( + IN VIRTIO_DEVICE_PROTOCOL *This, + IN UINT64 Features + ) +{ + VIRTIO_1_0_DEV *Dev; + UINT32 Selector; + UINT32 Features32[2]; + + Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This); + + Features32[0] = (UINT32)Features; + Features32[1] = (UINT32)RShiftU64 (Features, 32); + + for (Selector = 0; Selector < 2; ++Selector) { + EFI_STATUS Status; + + // + // Select the low or high half of the features. + // + Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE, + OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DriverFeatureSelect), + sizeof Selector, &Selector); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Write that half. + // + Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE, + OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DriverFeature), + sizeof Features32[Selector], &Features32[Selector]); + if (EFI_ERROR (Status)) { + return Status; + } + } + + return EFI_SUCCESS; +} + + +STATIC +EFI_STATUS +EFIAPI +Virtio10SetQueueAddress ( + IN VIRTIO_DEVICE_PROTOCOL *This, + IN VRING *Ring + ) +{ + VIRTIO_1_0_DEV *Dev; + EFI_STATUS Status; + UINT64 Address; + UINT16 Enable; + + Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This); + + Address = (UINTN)Ring->Desc; + Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE, + OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueDesc), + sizeof Address, &Address); + if (EFI_ERROR (Status)) { + return Status; + } + + Address = (UINTN)Ring->Avail.Flags; + Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE, + OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueAvail), + sizeof Address, &Address); + if (EFI_ERROR (Status)) { + return Status; + } + + Address = (UINTN)Ring->Used.Flags; + Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE, + OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueUsed), + sizeof Address, &Address); + if (EFI_ERROR (Status)) { + return Status; + } + + Enable = 1; + Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE, + OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueEnable), + sizeof Enable, &Enable); + return Status; +} + + +STATIC +EFI_STATUS +EFIAPI +Virtio10SetQueueSel ( + IN VIRTIO_DEVICE_PROTOCOL *This, + IN UINT16 Index + ) +{ + VIRTIO_1_0_DEV *Dev; + EFI_STATUS Status; + + Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This); + + Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE, + OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueSelect), + sizeof Index, &Index); + return Status; +} + + +STATIC +EFI_STATUS +EFIAPI +Virtio10SetQueueNotify ( + IN VIRTIO_DEVICE_PROTOCOL *This, + IN UINT16 Index + ) +{ + VIRTIO_1_0_DEV *Dev; + EFI_STATUS Status; + UINT16 SavedQueueSelect; + UINT16 NotifyOffset; + + Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This); + + // + // Read NotifyOffset first. NotifyOffset is queue specific, so we have + // to stash & restore the current queue selector around it. + // + // So, start with saving the current queue selector. + // + Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, FALSE, + OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueSelect), + sizeof SavedQueueSelect, &SavedQueueSelect); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Select the requested queue. + // + Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE, + OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueSelect), + sizeof Index, &Index); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Read the QueueNotifyOff field. + // + Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, FALSE, + OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueNotifyOff), + sizeof NotifyOffset, &NotifyOffset); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Re-select the original queue. + // + Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE, + OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueSelect), + sizeof SavedQueueSelect, &SavedQueueSelect); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // We can now kick the queue. + // + Status = Virtio10Transfer (Dev->PciIo, &Dev->NotifyConfig, TRUE, + NotifyOffset * Dev->NotifyOffsetMultiplier, + sizeof Index, &Index); + return Status; +} + + +STATIC +EFI_STATUS +EFIAPI +Virtio10SetQueueAlign ( + IN VIRTIO_DEVICE_PROTOCOL *This, + IN UINT32 Alignment + ) +{ + return (Alignment == EFI_PAGE_SIZE) ? EFI_SUCCESS : EFI_UNSUPPORTED; +} + + +STATIC +EFI_STATUS +EFIAPI +Virtio10SetPageSize ( + IN VIRTIO_DEVICE_PROTOCOL *This, + IN UINT32 PageSize + ) +{ + return (PageSize == EFI_PAGE_SIZE) ? EFI_SUCCESS : EFI_UNSUPPORTED; +} + + +STATIC +EFI_STATUS +EFIAPI +Virtio10GetQueueNumMax ( + IN VIRTIO_DEVICE_PROTOCOL *This, + OUT UINT16 *QueueNumMax + ) +{ + VIRTIO_1_0_DEV *Dev; + EFI_STATUS Status; + + Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This); + + Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, FALSE, + OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueSize), + sizeof *QueueNumMax, QueueNumMax); + return Status; +} + + +STATIC +EFI_STATUS +EFIAPI +Virtio10SetQueueNum ( + IN VIRTIO_DEVICE_PROTOCOL *This, + IN UINT16 QueueSize + ) +{ + EFI_STATUS Status; + UINT16 CurrentSize; + + // + // This member function is required for VirtIo MMIO, and a no-op in + // VirtIo PCI 0.9.5. In VirtIo 1.0, drivers can theoretically use this + // member to reduce memory consumption, but none of our drivers do. So + // just check that they set the size that is already in effect. + // + Status = Virtio10GetQueueNumMax (This, &CurrentSize); + if (EFI_ERROR (Status)) { + return Status; + } + return (CurrentSize == QueueSize) ? EFI_SUCCESS : EFI_UNSUPPORTED; +} + + +STATIC +EFI_STATUS +EFIAPI +Virtio10GetDeviceStatus ( + IN VIRTIO_DEVICE_PROTOCOL *This, + OUT UINT8 *DeviceStatus + ) +{ + VIRTIO_1_0_DEV *Dev; + EFI_STATUS Status; + + Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This); + + Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, FALSE, + OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DeviceStatus), + sizeof *DeviceStatus, DeviceStatus); + return Status; +} + + +STATIC +EFI_STATUS +EFIAPI +Virtio10SetDeviceStatus ( + IN VIRTIO_DEVICE_PROTOCOL *This, + IN UINT8 DeviceStatus + ) +{ + VIRTIO_1_0_DEV *Dev; + EFI_STATUS Status; + + Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This); + + Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE, + OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DeviceStatus), + sizeof DeviceStatus, &DeviceStatus); + return Status; +} + + +STATIC +EFI_STATUS +EFIAPI +Virtio10WriteDevice ( + IN VIRTIO_DEVICE_PROTOCOL *This, + IN UINTN FieldOffset, + IN UINTN FieldSize, + IN UINT64 Value + ) +{ + VIRTIO_1_0_DEV *Dev; + EFI_STATUS Status; + + Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This); + + Status = Virtio10Transfer (Dev->PciIo, &Dev->SpecificConfig, TRUE, + FieldOffset, FieldSize, &Value); + return Status; +} + + +STATIC +EFI_STATUS +EFIAPI +Virtio10ReadDevice ( + IN VIRTIO_DEVICE_PROTOCOL *This, + IN UINTN FieldOffset, + IN UINTN FieldSize, + IN UINTN BufferSize, + OUT VOID *Buffer + ) +{ + VIRTIO_1_0_DEV *Dev; + EFI_STATUS Status; + + if (FieldSize != BufferSize) { + return EFI_INVALID_PARAMETER; + } + + Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This); + + Status = Virtio10Transfer (Dev->PciIo, &Dev->SpecificConfig, FALSE, + FieldOffset, FieldSize, Buffer); + return Status; +} + + +STATIC CONST VIRTIO_DEVICE_PROTOCOL mVirtIoTemplate = { + VIRTIO_SPEC_REVISION (1, 0, 0), + 0, // SubSystemDeviceId, filled in dynamically + Virtio10GetDeviceFeatures, + Virtio10SetGuestFeatures, + Virtio10SetQueueAddress, + Virtio10SetQueueSel, + Virtio10SetQueueNotify, + Virtio10SetQueueAlign, + Virtio10SetPageSize, + Virtio10GetQueueNumMax, + Virtio10SetQueueNum, + Virtio10GetDeviceStatus, + Virtio10SetDeviceStatus, + Virtio10WriteDevice, + Virtio10ReadDevice +}; + + +// +// EFI_DRIVER_BINDING_PROTOCOL member functions +// + +STATIC +EFI_STATUS +EFIAPI +Virtio10BindingSupported ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath + ) +{ + EFI_STATUS Status; + EFI_PCI_IO_PROTOCOL *PciIo; + PCI_TYPE00 Pci; + + Status = gBS->OpenProtocol (DeviceHandle, &gEfiPciIoProtocolGuid, + (VOID **)&PciIo, This->DriverBindingHandle, + DeviceHandle, EFI_OPEN_PROTOCOL_BY_DRIVER); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, 0, + sizeof Pci / sizeof (UINT32), &Pci); + if (EFI_ERROR (Status)) { + goto CloseProtocol; + } + + // + // Recognize non-transitional modern devices. Also, we'll have to parse the + // PCI capability list, so make sure the CapabilityPtr field will be valid. + // + if (Pci.Hdr.VendorId == VIRTIO_VENDOR_ID && + Pci.Hdr.DeviceId >= 0x1040 && + Pci.Hdr.DeviceId <= 0x107F && + Pci.Hdr.RevisionID >= 0x01 && + Pci.Device.SubsystemID >= 0x40 && + (Pci.Hdr.Status & EFI_PCI_STATUS_CAPABILITY) != 0) { + Status = EFI_SUCCESS; + } else { + Status = EFI_UNSUPPORTED; + } + +CloseProtocol: + gBS->CloseProtocol (DeviceHandle, &gEfiPciIoProtocolGuid, + This->DriverBindingHandle, DeviceHandle); + + return Status; +} + + +STATIC +EFI_STATUS +EFIAPI +Virtio10BindingStart ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath + ) +{ + VIRTIO_1_0_DEV *Device; + EFI_STATUS Status; + PCI_TYPE00 Pci; + UINT64 SetAttributes; + + Device = AllocateZeroPool (sizeof *Device); + if (Device == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Device->Signature = VIRTIO_1_0_SIGNATURE; + CopyMem (&Device->VirtIo, &mVirtIoTemplate, sizeof mVirtIoTemplate); + + Status = gBS->OpenProtocol (DeviceHandle, &gEfiPciIoProtocolGuid, + (VOID **)&Device->PciIo, This->DriverBindingHandle, + DeviceHandle, EFI_OPEN_PROTOCOL_BY_DRIVER); + if (EFI_ERROR (Status)) { + goto FreeDevice; + } + + Status = Device->PciIo->Pci.Read (Device->PciIo, EfiPciIoWidthUint32, 0, + sizeof Pci / sizeof (UINT32), &Pci); + if (EFI_ERROR (Status)) { + goto ClosePciIo; + } + + Device->VirtIo.SubSystemDeviceId = Pci.Hdr.DeviceId - 0x1040; + + Status = ParseCapabilities (Device, Pci.Device.CapabilityPtr); + if (EFI_ERROR (Status)) { + goto ClosePciIo; + } + + Status = Device->PciIo->Attributes (Device->PciIo, + EfiPciIoAttributeOperationGet, 0, + &Device->OriginalPciAttributes); + if (EFI_ERROR (Status)) { + goto ClosePciIo; + } + + SetAttributes = 0; + UpdateAttributes (&Device->CommonConfig, &SetAttributes); + UpdateAttributes (&Device->NotifyConfig, &SetAttributes); + UpdateAttributes (&Device->SpecificConfig, &SetAttributes); + Status = Device->PciIo->Attributes (Device->PciIo, + EfiPciIoAttributeOperationEnable, SetAttributes, + NULL); + if (EFI_ERROR (Status)) { + goto ClosePciIo; + } + + Status = gBS->InstallProtocolInterface (&DeviceHandle, + &gVirtioDeviceProtocolGuid, EFI_NATIVE_INTERFACE, + &Device->VirtIo); + if (EFI_ERROR (Status)) { + goto RestorePciAttributes; + } + + return EFI_SUCCESS; + +RestorePciAttributes: + Device->PciIo->Attributes (Device->PciIo, EfiPciIoAttributeOperationSet, + Device->OriginalPciAttributes, NULL); + +ClosePciIo: + gBS->CloseProtocol (DeviceHandle, &gEfiPciIoProtocolGuid, + This->DriverBindingHandle, DeviceHandle); + +FreeDevice: + FreePool (Device); + + return Status; +} + + +STATIC +EFI_STATUS +EFIAPI +Virtio10BindingStop ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN UINTN NumberOfChildren, + IN EFI_HANDLE *ChildHandleBuffer + ) +{ + EFI_STATUS Status; + VIRTIO_DEVICE_PROTOCOL *VirtIo; + VIRTIO_1_0_DEV *Device; + + Status = gBS->OpenProtocol (DeviceHandle, &gVirtioDeviceProtocolGuid, + (VOID **)&VirtIo, This->DriverBindingHandle, + DeviceHandle, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR (Status)) { + return Status; + } + + Device = VIRTIO_1_0_FROM_VIRTIO_DEVICE (VirtIo); + + Status = gBS->UninstallProtocolInterface (DeviceHandle, + &gVirtioDeviceProtocolGuid, &Device->VirtIo); + if (EFI_ERROR (Status)) { + return Status; + } + + Device->PciIo->Attributes (Device->PciIo, EfiPciIoAttributeOperationSet, + Device->OriginalPciAttributes, NULL); + gBS->CloseProtocol (DeviceHandle, &gEfiPciIoProtocolGuid, + This->DriverBindingHandle, DeviceHandle); + FreePool (Device); + + return EFI_SUCCESS; +} + + +STATIC EFI_DRIVER_BINDING_PROTOCOL mDriverBinding = { + &Virtio10BindingSupported, + &Virtio10BindingStart, + &Virtio10BindingStop, + 0x10, // Version + NULL, // ImageHandle, to be overwritten + NULL // DriverBindingHandle, to be overwritten +}; + + +// +// EFI_COMPONENT_NAME_PROTOCOL and EFI_COMPONENT_NAME2_PROTOCOL +// implementations +// + +STATIC +EFI_UNICODE_STRING_TABLE mDriverNameTable[] = { + { "eng;en", L"Virtio 1.0 PCI Driver" }, + { NULL, NULL } +}; + +STATIC +EFI_COMPONENT_NAME_PROTOCOL mComponentName; + +STATIC +EFI_STATUS +EFIAPI +Virtio10GetDriverName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN CHAR8 *Language, + OUT CHAR16 **DriverName + ) +{ + return LookupUnicodeString2 ( + Language, + This->SupportedLanguages, + mDriverNameTable, + DriverName, + (BOOLEAN)(This == &mComponentName) // Iso639Language + ); +} + +STATIC +EFI_STATUS +EFIAPI +Virtio10GetDeviceName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN EFI_HANDLE ChildHandle, + IN CHAR8 *Language, + OUT CHAR16 **ControllerName + ) +{ + return EFI_UNSUPPORTED; +} + +STATIC +EFI_COMPONENT_NAME_PROTOCOL mComponentName = { + &Virtio10GetDriverName, + &Virtio10GetDeviceName, + "eng" +}; + +STATIC +EFI_COMPONENT_NAME2_PROTOCOL mComponentName2 = { + (EFI_COMPONENT_NAME2_GET_DRIVER_NAME) &Virtio10GetDriverName, + (EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME) &Virtio10GetDeviceName, + "en" +}; + + +// +// Entry point of this driver +// + +EFI_STATUS +EFIAPI +Virtio10EntryPoint ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + return EfiLibInstallDriverBindingComponentName2 ( + ImageHandle, + SystemTable, + &mDriverBinding, + ImageHandle, + &mComponentName, + &mComponentName2 + ); +}