audk/IntelSiliconPkg/Feature/VTd/IntelVTdDxe/DmaProtection.c

673 lines
21 KiB
C

/** @file
Copyright (c) 2017 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "DmaProtection.h"
UINT64 mBelow4GMemoryLimit;
UINT64 mAbove4GMemoryLimit;
EDKII_PLATFORM_VTD_POLICY_PROTOCOL *mPlatformVTdPolicy;
VTD_ACCESS_REQUEST *mAccessRequest = NULL;
UINTN mAccessRequestCount = 0;
UINTN mAccessRequestMaxCount = 0;
/**
Append VTd Access Request to global.
@param[in] Segment The Segment used to identify a VTd engine.
@param[in] SourceId The SourceId used to identify a VTd engine and table entry.
@param[in] BaseAddress The base of device memory address to be used as the DMA memory.
@param[in] Length The length of device memory address to be used as the DMA memory.
@param[in] IoMmuAccess The IOMMU access.
@retval EFI_SUCCESS The IoMmuAccess is set for the memory range specified by BaseAddress and Length.
@retval EFI_INVALID_PARAMETER BaseAddress is not IoMmu Page size aligned.
@retval EFI_INVALID_PARAMETER Length is not IoMmu Page size aligned.
@retval EFI_INVALID_PARAMETER Length is 0.
@retval EFI_INVALID_PARAMETER IoMmuAccess specified an illegal combination of access.
@retval EFI_UNSUPPORTED The bit mask of IoMmuAccess is not supported by the IOMMU.
@retval EFI_UNSUPPORTED The IOMMU does not support the memory range specified by BaseAddress and Length.
@retval EFI_OUT_OF_RESOURCES There are not enough resources available to modify the IOMMU access.
@retval EFI_DEVICE_ERROR The IOMMU device reported an error while attempting the operation.
**/
EFI_STATUS
RequestAccessAttribute (
IN UINT16 Segment,
IN VTD_SOURCE_ID SourceId,
IN UINT64 BaseAddress,
IN UINT64 Length,
IN UINT64 IoMmuAccess
)
{
VTD_ACCESS_REQUEST *NewAccessRequest;
UINTN Index;
//
// Optimization for memory.
//
// If the last record is to IoMmuAccess=0,
// Check previous records and remove the matched entry.
//
if (IoMmuAccess == 0) {
for (Index = 0; Index < mAccessRequestCount; Index++) {
if ((mAccessRequest[Index].Segment == Segment) &&
(mAccessRequest[Index].SourceId.Uint16 == SourceId.Uint16) &&
(mAccessRequest[Index].BaseAddress == BaseAddress) &&
(mAccessRequest[Index].Length == Length) &&
(mAccessRequest[Index].IoMmuAccess != 0)) {
//
// Remove this record [Index].
// No need to add the new record.
//
if (Index != mAccessRequestCount - 1) {
CopyMem (
&mAccessRequest[Index],
&mAccessRequest[Index + 1],
sizeof (VTD_ACCESS_REQUEST) * (mAccessRequestCount - 1 - Index)
);
}
ZeroMem (&mAccessRequest[mAccessRequestCount - 1], sizeof(VTD_ACCESS_REQUEST));
mAccessRequestCount--;
return EFI_SUCCESS;
}
}
}
if (mAccessRequestCount >= mAccessRequestMaxCount) {
NewAccessRequest = AllocateZeroPool (sizeof(*NewAccessRequest) * (mAccessRequestMaxCount + MAX_VTD_ACCESS_REQUEST));
if (NewAccessRequest == NULL) {
return EFI_OUT_OF_RESOURCES;
}
mAccessRequestMaxCount += MAX_VTD_ACCESS_REQUEST;
if (mAccessRequest != NULL) {
CopyMem (NewAccessRequest, mAccessRequest, sizeof(*NewAccessRequest) * mAccessRequestCount);
FreePool (mAccessRequest);
}
mAccessRequest = NewAccessRequest;
}
ASSERT (mAccessRequestCount < mAccessRequestMaxCount);
mAccessRequest[mAccessRequestCount].Segment = Segment;
mAccessRequest[mAccessRequestCount].SourceId = SourceId;
mAccessRequest[mAccessRequestCount].BaseAddress = BaseAddress;
mAccessRequest[mAccessRequestCount].Length = Length;
mAccessRequest[mAccessRequestCount].IoMmuAccess = IoMmuAccess;
mAccessRequestCount++;
return EFI_SUCCESS;
}
/**
Process Access Requests from before DMAR table is installed.
**/
VOID
ProcessRequestedAccessAttribute (
VOID
)
{
UINTN Index;
EFI_STATUS Status;
DEBUG ((DEBUG_INFO, "ProcessRequestedAccessAttribute ...\n"));
for (Index = 0; Index < mAccessRequestCount; Index++) {
DEBUG ((
DEBUG_INFO,
"PCI(S%x.B%x.D%x.F%x) ",
mAccessRequest[Index].Segment,
mAccessRequest[Index].SourceId.Bits.Bus,
mAccessRequest[Index].SourceId.Bits.Device,
mAccessRequest[Index].SourceId.Bits.Function
));
DEBUG ((
DEBUG_INFO,
"(0x%lx~0x%lx) - %lx\n",
mAccessRequest[Index].BaseAddress,
mAccessRequest[Index].Length,
mAccessRequest[Index].IoMmuAccess
));
Status = SetAccessAttribute (
mAccessRequest[Index].Segment,
mAccessRequest[Index].SourceId,
mAccessRequest[Index].BaseAddress,
mAccessRequest[Index].Length,
mAccessRequest[Index].IoMmuAccess
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "SetAccessAttribute %r: ", Status));
}
}
if (mAccessRequest != NULL) {
FreePool (mAccessRequest);
}
mAccessRequest = NULL;
mAccessRequestCount = 0;
mAccessRequestMaxCount = 0;
DEBUG ((DEBUG_INFO, "ProcessRequestedAccessAttribute Done\n"));
}
/**
return the UEFI memory information.
@param[out] Below4GMemoryLimit The below 4GiB memory limit
@param[out] Above4GMemoryLimit The above 4GiB memory limit
**/
VOID
ReturnUefiMemoryMap (
OUT UINT64 *Below4GMemoryLimit,
OUT UINT64 *Above4GMemoryLimit
)
{
EFI_STATUS Status;
EFI_MEMORY_DESCRIPTOR *EfiMemoryMap;
EFI_MEMORY_DESCRIPTOR *EfiMemoryMapEnd;
EFI_MEMORY_DESCRIPTOR *EfiEntry;
EFI_MEMORY_DESCRIPTOR *NextEfiEntry;
EFI_MEMORY_DESCRIPTOR TempEfiEntry;
UINTN EfiMemoryMapSize;
UINTN EfiMapKey;
UINTN EfiDescriptorSize;
UINT32 EfiDescriptorVersion;
UINT64 MemoryBlockLength;
*Below4GMemoryLimit = 0;
*Above4GMemoryLimit = 0;
//
// Get the EFI memory map.
//
EfiMemoryMapSize = 0;
EfiMemoryMap = NULL;
Status = gBS->GetMemoryMap (
&EfiMemoryMapSize,
EfiMemoryMap,
&EfiMapKey,
&EfiDescriptorSize,
&EfiDescriptorVersion
);
ASSERT (Status == EFI_BUFFER_TOO_SMALL);
do {
//
// Use size returned back plus 1 descriptor for the AllocatePool.
// We don't just multiply by 2 since the "for" loop below terminates on
// EfiMemoryMapEnd which is dependent upon EfiMemoryMapSize. Otherwize
// we process bogus entries and create bogus E820 entries.
//
EfiMemoryMap = (EFI_MEMORY_DESCRIPTOR *) AllocatePool (EfiMemoryMapSize);
ASSERT (EfiMemoryMap != NULL);
Status = gBS->GetMemoryMap (
&EfiMemoryMapSize,
EfiMemoryMap,
&EfiMapKey,
&EfiDescriptorSize,
&EfiDescriptorVersion
);
if (EFI_ERROR (Status)) {
FreePool (EfiMemoryMap);
}
} while (Status == EFI_BUFFER_TOO_SMALL);
ASSERT_EFI_ERROR (Status);
//
// Sort memory map from low to high
//
EfiEntry = EfiMemoryMap;
NextEfiEntry = NEXT_MEMORY_DESCRIPTOR (EfiEntry, EfiDescriptorSize);
EfiMemoryMapEnd = (EFI_MEMORY_DESCRIPTOR *) ((UINT8 *) EfiMemoryMap + EfiMemoryMapSize);
while (EfiEntry < EfiMemoryMapEnd) {
while (NextEfiEntry < EfiMemoryMapEnd) {
if (EfiEntry->PhysicalStart > NextEfiEntry->PhysicalStart) {
CopyMem (&TempEfiEntry, EfiEntry, sizeof (EFI_MEMORY_DESCRIPTOR));
CopyMem (EfiEntry, NextEfiEntry, sizeof (EFI_MEMORY_DESCRIPTOR));
CopyMem (NextEfiEntry, &TempEfiEntry, sizeof (EFI_MEMORY_DESCRIPTOR));
}
NextEfiEntry = NEXT_MEMORY_DESCRIPTOR (NextEfiEntry, EfiDescriptorSize);
}
EfiEntry = NEXT_MEMORY_DESCRIPTOR (EfiEntry, EfiDescriptorSize);
NextEfiEntry = NEXT_MEMORY_DESCRIPTOR (EfiEntry, EfiDescriptorSize);
}
//
//
//
DEBUG ((DEBUG_INFO, "MemoryMap:\n"));
EfiEntry = EfiMemoryMap;
EfiMemoryMapEnd = (EFI_MEMORY_DESCRIPTOR *) ((UINT8 *) EfiMemoryMap + EfiMemoryMapSize);
while (EfiEntry < EfiMemoryMapEnd) {
MemoryBlockLength = (UINT64) (LShiftU64 (EfiEntry->NumberOfPages, 12));
DEBUG ((DEBUG_INFO, "Entry(0x%02x) 0x%016lx - 0x%016lx\n", EfiEntry->Type, EfiEntry->PhysicalStart, EfiEntry->PhysicalStart + MemoryBlockLength));
switch (EfiEntry->Type) {
case EfiLoaderCode:
case EfiLoaderData:
case EfiBootServicesCode:
case EfiBootServicesData:
case EfiConventionalMemory:
case EfiRuntimeServicesCode:
case EfiRuntimeServicesData:
case EfiACPIReclaimMemory:
case EfiACPIMemoryNVS:
case EfiReservedMemoryType:
if ((EfiEntry->PhysicalStart + MemoryBlockLength) <= BASE_1MB) {
//
// Skip the memory block is under 1MB
//
} else if (EfiEntry->PhysicalStart >= BASE_4GB) {
if (*Above4GMemoryLimit < EfiEntry->PhysicalStart + MemoryBlockLength) {
*Above4GMemoryLimit = EfiEntry->PhysicalStart + MemoryBlockLength;
}
} else {
if (*Below4GMemoryLimit < EfiEntry->PhysicalStart + MemoryBlockLength) {
*Below4GMemoryLimit = EfiEntry->PhysicalStart + MemoryBlockLength;
}
}
break;
}
EfiEntry = NEXT_MEMORY_DESCRIPTOR (EfiEntry, EfiDescriptorSize);
}
FreePool (EfiMemoryMap);
DEBUG ((DEBUG_INFO, "Result:\n"));
DEBUG ((DEBUG_INFO, "Below4GMemoryLimit: 0x%016lx\n", *Below4GMemoryLimit));
DEBUG ((DEBUG_INFO, "Above4GMemoryLimit: 0x%016lx\n", *Above4GMemoryLimit));
return ;
}
/**
The scan bus callback function to always enable page attribute.
@param[in] Context The context of the callback.
@param[in] Segment The segment of the source.
@param[in] Bus The bus of the source.
@param[in] Device The device of the source.
@param[in] Function The function of the source.
@retval EFI_SUCCESS The VTd entry is updated to always enable all DMA access for the specific device.
**/
EFI_STATUS
EFIAPI
ScanBusCallbackAlwaysEnablePageAttribute (
IN VOID *Context,
IN UINT16 Segment,
IN UINT8 Bus,
IN UINT8 Device,
IN UINT8 Function
)
{
VTD_SOURCE_ID SourceId;
EFI_STATUS Status;
SourceId.Bits.Bus = Bus;
SourceId.Bits.Device = Device;
SourceId.Bits.Function = Function;
Status = AlwaysEnablePageAttribute (Segment, SourceId);
return Status;
}
/**
Always enable the VTd page attribute for the device in the DeviceScope.
@param[in] DeviceScope the input device scope data structure
@retval EFI_SUCCESS The VTd entry is updated to always enable all DMA access for the specific device in the device scope.
**/
EFI_STATUS
AlwaysEnablePageAttributeDeviceScope (
IN EDKII_PLATFORM_VTD_DEVICE_SCOPE *DeviceScope
)
{
UINT8 Bus;
UINT8 Device;
UINT8 Function;
VTD_SOURCE_ID SourceId;
UINT8 SecondaryBusNumber;
EFI_STATUS Status;
Status = GetPciBusDeviceFunction (DeviceScope->SegmentNumber, &DeviceScope->DeviceScope, &Bus, &Device, &Function);
if (DeviceScope->DeviceScope.Type == EFI_ACPI_DEVICE_SCOPE_ENTRY_TYPE_PCI_BRIDGE) {
//
// Need scan the bridge and add all devices.
//
SecondaryBusNumber = PciSegmentRead8 (PCI_SEGMENT_LIB_ADDRESS(DeviceScope->SegmentNumber, Bus, Device, Function, PCI_BRIDGE_SECONDARY_BUS_REGISTER_OFFSET));
Status = ScanPciBus (NULL, DeviceScope->SegmentNumber, SecondaryBusNumber, ScanBusCallbackAlwaysEnablePageAttribute);
return Status;
} else {
SourceId.Bits.Bus = Bus;
SourceId.Bits.Device = Device;
SourceId.Bits.Function = Function;
Status = AlwaysEnablePageAttribute (DeviceScope->SegmentNumber, SourceId);
return Status;
}
}
/**
Always enable the VTd page attribute for the device matching DeviceId.
@param[in] PciDeviceId the input PCI device ID
@retval EFI_SUCCESS The VTd entry is updated to always enable all DMA access for the specific device matching DeviceId.
**/
EFI_STATUS
AlwaysEnablePageAttributePciDeviceId (
IN EDKII_PLATFORM_VTD_PCI_DEVICE_ID *PciDeviceId
)
{
UINTN VtdIndex;
UINTN PciIndex;
PCI_DEVICE_DATA *PciDeviceData;
EFI_STATUS Status;
for (VtdIndex = 0; VtdIndex < mVtdUnitNumber; VtdIndex++) {
for (PciIndex = 0; PciIndex < mVtdUnitInformation[VtdIndex].PciDeviceInfo.PciDeviceDataNumber; PciIndex++) {
PciDeviceData = &mVtdUnitInformation[VtdIndex].PciDeviceInfo.PciDeviceData[PciIndex];
if (((PciDeviceId->VendorId == 0xFFFF) || (PciDeviceId->VendorId == PciDeviceData->PciDeviceId.VendorId)) &&
((PciDeviceId->DeviceId == 0xFFFF) || (PciDeviceId->DeviceId == PciDeviceData->PciDeviceId.DeviceId)) &&
((PciDeviceId->RevisionId == 0xFF) || (PciDeviceId->RevisionId == PciDeviceData->PciDeviceId.RevisionId)) &&
((PciDeviceId->SubsystemVendorId == 0xFFFF) || (PciDeviceId->SubsystemVendorId == PciDeviceData->PciDeviceId.SubsystemVendorId)) &&
((PciDeviceId->SubsystemDeviceId == 0xFFFF) || (PciDeviceId->SubsystemDeviceId == PciDeviceData->PciDeviceId.SubsystemDeviceId)) ) {
Status = AlwaysEnablePageAttribute (mVtdUnitInformation[VtdIndex].Segment, PciDeviceData->PciSourceId);
if (EFI_ERROR(Status)) {
continue;
}
}
}
}
return EFI_SUCCESS;
}
/**
Always enable the VTd page attribute for the device.
@param[in] DeviceInfo the exception device information
@retval EFI_SUCCESS The VTd entry is updated to always enable all DMA access for the specific device in the device info.
**/
EFI_STATUS
AlwaysEnablePageAttributeExceptionDeviceInfo (
IN EDKII_PLATFORM_VTD_EXCEPTION_DEVICE_INFO *DeviceInfo
)
{
switch (DeviceInfo->Type) {
case EDKII_PLATFORM_VTD_EXCEPTION_DEVICE_INFO_TYPE_DEVICE_SCOPE:
return AlwaysEnablePageAttributeDeviceScope ((VOID *)(DeviceInfo + 1));
case EDKII_PLATFORM_VTD_EXCEPTION_DEVICE_INFO_TYPE_PCI_DEVICE_ID:
return AlwaysEnablePageAttributePciDeviceId ((VOID *)(DeviceInfo + 1));
default:
return EFI_UNSUPPORTED;
}
}
/**
Initialize platform VTd policy.
**/
VOID
InitializePlatformVTdPolicy (
VOID
)
{
EFI_STATUS Status;
UINTN DeviceInfoCount;
VOID *DeviceInfo;
EDKII_PLATFORM_VTD_EXCEPTION_DEVICE_INFO *ThisDeviceInfo;
UINTN Index;
//
// It is optional.
//
Status = gBS->LocateProtocol (
&gEdkiiPlatformVTdPolicyProtocolGuid,
NULL,
(VOID **)&mPlatformVTdPolicy
);
if (!EFI_ERROR(Status)) {
DEBUG ((DEBUG_INFO, "InitializePlatformVTdPolicy\n"));
Status = mPlatformVTdPolicy->GetExceptionDeviceList (mPlatformVTdPolicy, &DeviceInfoCount, &DeviceInfo);
if (!EFI_ERROR(Status)) {
ThisDeviceInfo = DeviceInfo;
for (Index = 0; Index < DeviceInfoCount; Index++) {
if (ThisDeviceInfo->Type == EDKII_PLATFORM_VTD_EXCEPTION_DEVICE_INFO_TYPE_END) {
break;
}
AlwaysEnablePageAttributeExceptionDeviceInfo (ThisDeviceInfo);
ThisDeviceInfo = (VOID *)((UINTN)ThisDeviceInfo + ThisDeviceInfo->Length);
}
FreePool (DeviceInfo);
}
}
}
/**
Setup VTd engine.
**/
VOID
SetupVtd (
VOID
)
{
EFI_STATUS Status;
VOID *PciEnumerationComplete;
UINTN Index;
UINT64 Below4GMemoryLimit;
UINT64 Above4GMemoryLimit;
//
// PCI Enumeration must be done
//
Status = gBS->LocateProtocol (
&gEfiPciEnumerationCompleteProtocolGuid,
NULL,
&PciEnumerationComplete
);
ASSERT_EFI_ERROR (Status);
ReturnUefiMemoryMap (&Below4GMemoryLimit, &Above4GMemoryLimit);
Below4GMemoryLimit = ALIGN_VALUE_UP(Below4GMemoryLimit, SIZE_256MB);
DEBUG ((DEBUG_INFO, " Adjusted Below4GMemoryLimit: 0x%016lx\n", Below4GMemoryLimit));
mBelow4GMemoryLimit = Below4GMemoryLimit;
mAbove4GMemoryLimit = Above4GMemoryLimit;
//
// 1. setup
//
DEBUG ((DEBUG_INFO, "ParseDmarAcpiTable\n"));
Status = ParseDmarAcpiTableDrhd ();
if (EFI_ERROR (Status)) {
return;
}
DEBUG ((DEBUG_INFO, "PrepareVtdConfig\n"));
PrepareVtdConfig ();
//
// 2. initialization
//
DEBUG ((DEBUG_INFO, "SetupTranslationTable\n"));
Status = SetupTranslationTable ();
if (EFI_ERROR (Status)) {
return;
}
InitializePlatformVTdPolicy ();
ParseDmarAcpiTableRmrr ();
if ((PcdGet8 (PcdVTdPolicyPropertyMask) & BIT2) == 0) {
//
// Support IOMMU access attribute request recording before DMAR table is installed.
// Here is to process the requests.
//
ProcessRequestedAccessAttribute ();
}
for (Index = 0; Index < mVtdUnitNumber; Index++) {
DEBUG ((DEBUG_INFO,"VTD Unit %d (Segment: %04x)\n", Index, mVtdUnitInformation[Index].Segment));
if (mVtdUnitInformation[Index].ExtRootEntryTable != NULL) {
DumpDmarExtContextEntryTable (mVtdUnitInformation[Index].ExtRootEntryTable);
}
if (mVtdUnitInformation[Index].RootEntryTable != NULL) {
DumpDmarContextEntryTable (mVtdUnitInformation[Index].RootEntryTable);
}
}
//
// 3. enable
//
DEBUG ((DEBUG_INFO, "EnableDmar\n"));
Status = EnableDmar ();
if (EFI_ERROR (Status)) {
return;
}
DEBUG ((DEBUG_INFO, "DumpVtdRegs\n"));
DumpVtdRegsAll ();
}
/**
Notification function of ACPI Table change.
This is a notification function registered on ACPI Table change event.
@param Event Event whose notification function is being invoked.
@param Context Pointer to the notification function's context.
**/
VOID
EFIAPI
AcpiNotificationFunc (
IN EFI_EVENT Event,
IN VOID *Context
)
{
EFI_STATUS Status;
Status = GetDmarAcpiTable ();
if (EFI_ERROR (Status)) {
if (Status == EFI_ALREADY_STARTED) {
gBS->CloseEvent (Event);
}
return;
}
SetupVtd ();
gBS->CloseEvent (Event);
}
/**
Exit boot service callback function.
@param[in] Event The event handle.
@param[in] Context The event content.
**/
VOID
EFIAPI
OnExitBootServices (
IN EFI_EVENT Event,
IN VOID *Context
)
{
DEBUG ((DEBUG_INFO, "Vtd OnExitBootServices\n"));
DumpVtdRegsAll ();
if ((PcdGet8(PcdVTdPolicyPropertyMask) & BIT1) == 0) {
DisableDmar ();
DumpVtdRegsAll ();
}
}
/**
Legacy boot callback function.
@param[in] Event The event handle.
@param[in] Context The event content.
**/
VOID
EFIAPI
OnLegacyBoot (
EFI_EVENT Event,
VOID *Context
)
{
DEBUG ((DEBUG_INFO, "Vtd OnLegacyBoot\n"));
DumpVtdRegsAll ();
DisableDmar ();
DumpVtdRegsAll ();
}
/**
Initialize DMA protection.
**/
VOID
InitializeDmaProtection (
VOID
)
{
EFI_STATUS Status;
EFI_EVENT ExitBootServicesEvent;
EFI_EVENT LegacyBootEvent;
EFI_EVENT EventAcpi10;
EFI_EVENT EventAcpi20;
Status = gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
VTD_TPL_LEVEL,
AcpiNotificationFunc,
NULL,
&gEfiAcpi10TableGuid,
&EventAcpi10
);
ASSERT_EFI_ERROR (Status);
Status = gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
VTD_TPL_LEVEL,
AcpiNotificationFunc,
NULL,
&gEfiAcpi20TableGuid,
&EventAcpi20
);
ASSERT_EFI_ERROR (Status);
//
// Signal the events initially for the case
// that DMAR table has been installed.
//
gBS->SignalEvent (EventAcpi20);
gBS->SignalEvent (EventAcpi10);
Status = gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
OnExitBootServices,
NULL,
&gEfiEventExitBootServicesGuid,
&ExitBootServicesEvent
);
ASSERT_EFI_ERROR (Status);
Status = EfiCreateEventLegacyBootEx (
TPL_CALLBACK,
OnLegacyBoot,
NULL,
&LegacyBootEvent
);
ASSERT_EFI_ERROR (Status);
return ;
}