/** @file | |
A non-transitional driver for VirtIo 1.0 PCI devices. | |
Copyright (C) 2016, Red Hat, Inc. | |
Copyright (C) 2017, AMD Inc, All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <IndustryStandard/Pci.h> | |
#include <IndustryStandard/Virtio.h> | |
#include <Protocol/PciIo.h> | |
#include <Protocol/PciRootBridgeIo.h> | |
#include <Protocol/VirtioDevice.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Library/PciCapLib.h> | |
#include <Library/PciCapPciIoLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
#include <Library/UefiLib.h> | |
#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; | |
} | |
/* | |
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. | |
@retval EFI_SUCCESS Traversal successful. | |
@return Error codes from PciCapPciIoLib, PciCapLib, and the | |
GetBarType() helper function. | |
*/ | |
STATIC | |
EFI_STATUS | |
ParseCapabilities ( | |
IN OUT VIRTIO_1_0_DEV *Device | |
) | |
{ | |
EFI_STATUS Status; | |
PCI_CAP_DEV *PciDevice; | |
PCI_CAP_LIST *CapList; | |
UINT16 VendorInstance; | |
PCI_CAP *VendorCap; | |
Status = PciCapPciIoDeviceInit (Device->PciIo, &PciDevice); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = PciCapListInit (PciDevice, &CapList); | |
if (EFI_ERROR (Status)) { | |
goto UninitPciDevice; | |
} | |
for (VendorInstance = 0; | |
!EFI_ERROR ( | |
PciCapListFindCap ( | |
CapList, | |
PciCapNormal, | |
EFI_PCI_CAPABILITY_ID_VENDOR, | |
VendorInstance, | |
&VendorCap | |
) | |
); | |
VendorInstance++) | |
{ | |
UINT8 CapLen; | |
VIRTIO_PCI_CAP VirtIoCap; | |
VIRTIO_1_0_CONFIG *ParsedConfig; | |
// | |
// Big enough to accommodate a VIRTIO_PCI_CAP structure? | |
// | |
Status = PciCapRead ( | |
PciDevice, | |
VendorCap, | |
OFFSET_OF (EFI_PCI_CAPABILITY_VENDOR_HDR, Length), | |
&CapLen, | |
sizeof CapLen | |
); | |
if (EFI_ERROR (Status)) { | |
goto UninitCapList; | |
} | |
if (CapLen < sizeof VirtIoCap) { | |
// | |
// Too small, move to next. | |
// | |
continue; | |
} | |
// | |
// Read interesting part of capability. | |
// | |
Status = PciCapRead (PciDevice, VendorCap, 0, &VirtIoCap, sizeof VirtIoCap); | |
if (EFI_ERROR (Status)) { | |
goto UninitCapList; | |
} | |
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)) { | |
goto UninitCapList; | |
} | |
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 VirtIoCap + sizeof Device->NotifyOffsetMultiplier) { | |
// | |
// Too small, move to next. | |
// | |
continue; | |
} | |
Status = PciCapRead ( | |
PciDevice, | |
VendorCap, | |
sizeof VirtIoCap, | |
&Device->NotifyOffsetMultiplier, | |
sizeof Device->NotifyOffsetMultiplier | |
); | |
if (EFI_ERROR (Status)) { | |
goto UninitCapList; | |
} | |
} | |
// | |
// Capability parsed successfully. | |
// | |
ParsedConfig->Exists = TRUE; | |
} | |
ASSERT_EFI_ERROR (Status); | |
UninitCapList: | |
PciCapListUninit (CapList); | |
UninitPciDevice: | |
PciCapPciIoDeviceUninit (PciDevice); | |
return Status; | |
} | |
/** | |
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, | |
IN UINT64 RingBaseShift | |
) | |
{ | |
VIRTIO_1_0_DEV *Dev; | |
EFI_STATUS Status; | |
UINT64 Address; | |
UINT16 Enable; | |
Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This); | |
Address = (UINTN)Ring->Desc; | |
Address += RingBaseShift; | |
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; | |
Address += RingBaseShift; | |
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; | |
Address += RingBaseShift; | |
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 | |
EFI_STATUS | |
EFIAPI | |
Virtio10AllocateSharedPages ( | |
IN VIRTIO_DEVICE_PROTOCOL *This, | |
IN UINTN Pages, | |
IN OUT VOID **HostAddress | |
) | |
{ | |
VIRTIO_1_0_DEV *Dev; | |
EFI_STATUS Status; | |
Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This); | |
Status = Dev->PciIo->AllocateBuffer ( | |
Dev->PciIo, | |
AllocateAnyPages, | |
EfiBootServicesData, | |
Pages, | |
HostAddress, | |
EFI_PCI_ATTRIBUTE_MEMORY_CACHED | |
); | |
return Status; | |
} | |
STATIC | |
VOID | |
EFIAPI | |
Virtio10FreeSharedPages ( | |
IN VIRTIO_DEVICE_PROTOCOL *This, | |
IN UINTN Pages, | |
IN VOID *HostAddress | |
) | |
{ | |
VIRTIO_1_0_DEV *Dev; | |
Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This); | |
Dev->PciIo->FreeBuffer ( | |
Dev->PciIo, | |
Pages, | |
HostAddress | |
); | |
} | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
Virtio10MapSharedBuffer ( | |
IN VIRTIO_DEVICE_PROTOCOL *This, | |
IN VIRTIO_MAP_OPERATION Operation, | |
IN VOID *HostAddress, | |
IN OUT UINTN *NumberOfBytes, | |
OUT EFI_PHYSICAL_ADDRESS *DeviceAddress, | |
OUT VOID **Mapping | |
) | |
{ | |
EFI_STATUS Status; | |
VIRTIO_1_0_DEV *Dev; | |
EFI_PCI_IO_PROTOCOL_OPERATION PciIoOperation; | |
Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This); | |
// | |
// Map VIRTIO_MAP_OPERATION to EFI_PCI_IO_PROTOCOL_OPERATION | |
// | |
switch (Operation) { | |
case VirtioOperationBusMasterRead: | |
PciIoOperation = EfiPciIoOperationBusMasterRead; | |
break; | |
case VirtioOperationBusMasterWrite: | |
PciIoOperation = EfiPciIoOperationBusMasterWrite; | |
break; | |
case VirtioOperationBusMasterCommonBuffer: | |
PciIoOperation = EfiPciIoOperationBusMasterCommonBuffer; | |
break; | |
default: | |
return EFI_INVALID_PARAMETER; | |
} | |
Status = Dev->PciIo->Map ( | |
Dev->PciIo, | |
PciIoOperation, | |
HostAddress, | |
NumberOfBytes, | |
DeviceAddress, | |
Mapping | |
); | |
return Status; | |
} | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
Virtio10UnmapSharedBuffer ( | |
IN VIRTIO_DEVICE_PROTOCOL *This, | |
IN VOID *Mapping | |
) | |
{ | |
EFI_STATUS Status; | |
VIRTIO_1_0_DEV *Dev; | |
Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This); | |
Status = Dev->PciIo->Unmap ( | |
Dev->PciIo, | |
Mapping | |
); | |
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, | |
Virtio10AllocateSharedPages, | |
Virtio10FreeSharedPages, | |
Virtio10MapSharedBuffer, | |
Virtio10UnmapSharedBuffer | |
}; | |
// | |
// 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; | |
} | |
Status = EFI_UNSUPPORTED; | |
// | |
// 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)) | |
{ | |
// | |
// The virtio-vga device is special. It can be driven both as a VGA device | |
// with a linear framebuffer, and through its underlying, modern, | |
// virtio-gpu-pci device, which has no linear framebuffer itself. For | |
// compatibility with guest OSes that insist on inheriting a linear | |
// framebuffer from the firmware, we should leave virtio-vga to | |
// QemuVideoDxe, and support only virtio-gpu-pci here. | |
// | |
// Both virtio-vga and virtio-gpu-pci have DeviceId 0x1050, but only the | |
// former has device class PCI_CLASS_DISPLAY_VGA. | |
// | |
if ((Pci.Hdr.DeviceId != 0x1050) || !IS_PCI_VGA (&Pci)) { | |
Status = EFI_SUCCESS; | |
} | |
} | |
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); | |
if (EFI_ERROR (Status)) { | |
goto ClosePciIo; | |
} | |
Status = Device->PciIo->Attributes ( | |
Device->PciIo, | |
EfiPciIoAttributeOperationGet, | |
0, | |
&Device->OriginalPciAttributes | |
); | |
if (EFI_ERROR (Status)) { | |
goto ClosePciIo; | |
} | |
SetAttributes = (EFI_PCI_IO_ATTRIBUTE_BUS_MASTER | | |
EFI_PCI_IO_ATTRIBUTE_DUAL_ADDRESS_CYCLE); | |
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 | |
); | |
} |