CloverBootloader/OvmfPkg/Virtio10Dxe/Virtio10.c

1309 lines
33 KiB
C
Raw Normal View History

/** @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
);
}